Reputation: 37645
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
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
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
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
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