Paul Boddington
Paul Boddington

Reputation: 37645

How is the component type for the varargs array determined?

When you use varargs, how is the component type of the resulting array determined?

For example, is this program guaranteed to print true or is its behaviour technically unspecified?

public static void main(String[] args) {
    foo("", 0);
}

static <T> void foo(T... arr) {
    System.out.println(arr.getClass() == Serializable[].class);
} 

Upvotes: 15

Views: 212

Answers (4)

AdamSkywalker
AdamSkywalker

Reputation: 11609

I ran this code and the output tells you have no guarantees (at least if classes have more than one common ancestor on different hierarchy branches)

Honestly I don't know the reasons behind this magic, but I just could not post it as a comment

import java.util.*;
import java.lang.*;
import java.io.*;

class Ideone
{

  interface A{}
  interface B{}
  class AB implements A, B {}
  class BA implements A, B {}

    public static void main (String[] args) throws java.lang.Exception
    {
        foo(new AB(), new BA());
        foo2(new AB(), new BA());
    }

    static <T> void foo(T... arr) {
        System.out.println(arr.getClass() == A[].class);
    }

    static <T> void foo2(T... arr) {
        System.out.println(arr.getClass() == B[].class);
    } 
}

output

true
false

More weird things:

If interface B is declared before interface A, result is the opposite:

false
true

Changing order of arguments in method call, method declaration order and order of interfaces in implements block does not make effect for me (1.8.0_51).

Upvotes: 3

Alma Alma
Alma Alma

Reputation: 1722

Well, I am not 100% sure about this, but I think this has nothing to do with varargs, but more like dynamic binding, generics and type erasure.

"Arrays in Java are covariant, but generics are not. In other words, String[] is a subtype of Object[], but Stack is not a subtype of Stack."

Source: http://algs4.cs.princeton.edu/13stacks/

So to answer your question, this behavior is specified documented and expected.

In the book: 'Effective Java' Joshua Bloch explains it like this:

Arrays differ from generic types in two important ways. First, arrays are covariant. This scary-sounding word means simply that if Sub is a subtype of Super, then the array type Sub[] is a subtype of Super[]. Generics, by contrast, are invariant: for any two distinct types Type1 and Type2, List<Type1> is neither a subtype nor a supertype of List<Type2> [JLS, 4.10; Naftalin07, 2.5]. You might think this means that generics are deficient, but arguably it is arrays that are deficient.

And why am I talking about arrays you might ask? Well, because varargs will become arrays eventually.

"In past releases, a method that took an arbitrary number of values required you to create an array and put the values into the array prior to invoking the method."

"It is still true that multiple arguments must be passed in an array, but the varargs feature automates and hides the process."

Source: https://docs.oracle.com/javase/8/docs/technotes/guides/language/varargs.html

Upvotes: 1

Philipp
Philipp

Reputation: 1298

This answer might not be 100% the answer you are looking for, but maybe it helps. In general the arr.getClass() will return some array class (most general Object[].class, but could also be Integer[].class, Number[].class, or even Serializable[].class - normally the most specific type of all elements, but I wouldn't count on it see m-szalik answer). If you want to ensure that all classes contained in the array are an instance of Serializable you would have to check each element (btw. the presented implementation does not support null values):

static <T> void foo(T... arr) {
    System.out.println(Stream.of(arr)
            .filter(e -> !Serializable.class.isInstance(e.getClass()))
            .findFirst()
            .orElse(null) == null);
}

You may want to have a look at:

Btw. I agree with the opinion of Captain Fogetti:

Well, I am not 100% sure about this, but I think this has nothing to do with varargs, but more like dynamic binding, generics and type erasure.

NOTE:

Just some examples of your foo implementation:

  • foo(1, 2) would be Integer[].class
  • foo(1, 2.0) would be Number[].class
  • foo ("", 1) would be Serializable[].class
  • foo(null, 2) would be Integer[].class
  • foo("", new Object()) would be Object[].class

Upvotes: 2

m-szalik
m-szalik

Reputation: 3654

It is not about varargs but more about generics.
Generic information is lost during compilation. Your varargs parameter is converted by the compiler to an array. So JVM doesn't decide about the type but compiler does.

Your code is compiled to a bytecode as folows:

public static void main(String[] args) {
    foo(new Serializable[]{"", Integer.valueOf(0)});
}

The algorithm of decision is quite long, but you can read about it here. https://docs.oracle.com/javase/tutorial/java/generics/genTypeInference.html

Upvotes: 1

Related Questions