The following code is working fine for m2()
but is throwing a ClassCastException
when I use m1()
.
The only difference between m1
and m2
is the number of arguments.
public class Test {
public static void m1() {
m3(m4("1"));
}
public static void m2() {
m3(m4("1"), m4("2"));
}
public static void m3(Object... str) {
for (Object o : str) {
System.out.println(o);
}
}
public static <T> T m4(Object s) {
return (T) s;
}
public static void main(String[] args) {
m1();
}
}
My question is - Does varargs not work with a single argument when we use generics?
PS : This is not related to ClassCastException using Generics and Varargs
Let's skip the fact that you ignored an unchecked cast warning for now and try to understand why this happened.
In this statement:
Test.m3(Test.m4("1"));
There is one inferred type, which is the return type of m4
. If one is to use it outside the m3
invocation context, as in:
Test.m4("1"); // T is Object
T
is inferred as Object
. One can use a type witness to force the compiler to use a given type:
Test.<String>m4("1"); // T is String
...or by using the expression in an assignment context:
String resString = Test.m4("1"); // T is String
Integer resInt = Test.m4("1"); // T is Integer <-- see the problem?
... or in an invocation context:
Integer.parseInt(Test.m4("1")); // T is String
Long.toString(Test.m4("1")); // T is Long
Now, back to Test.m3(Test.m4("1"));
: I couldn't find a reference for this, but I believe the compiler is forced to make T
resolve to the parameter type of m3
, which is Object[]
. This means that T
, which has to coincide with the parameter type of m3
, is therefore resolved to Object[]
, and that makes it as though you specified generic types as:
Test.m3(Test.<Object[]>m4("1")); // this is what is happening
Now, because m4
is not returning an Object[]
, m3
is receiving a String
, which leads to the inescapable ClassCastException
.
The first way to fix this is to specify a correct type argument for m4
:
Test.m3(Test.<String>m4("1"));
With this, String
is the return type of m4
, and m3
is called with a single String
object (for the Object...
var-arg), as if you had written:
String temp = m4("1");
m3(temp);
The second approach was suggested in @Ravindra Ranwala's deleted answer. In my opinion, this boils down to heeding compiler warnings:
public static <T> T m4(Object s) {
return (T) s; // unchecked cast
}
The unchecked cast warning simply tells you that the compiler (and the runtime) are not going to enforce type compatibility, simply because T
is not known where you cast. The following version is type-safe, but it also makes the compiler use String
as the return type of m4
as well as the type of the parameter to m3
:
public static <T> T m4(T s) {
return s;
}
With this, m3(m4("1"));
still uses Object...
as the parameter type of m3
, while keeping String
the return type of m4
(i.e., a string value is used as the first element of the Object
array).
Because in the method implementation the array is only read and nothing is stored in the array
. However, if a method would store something in the array it could attempt to store an alien object in the array, like putting a HashMap<Long,Long>
into a HashMap<String,String>[]
. Neither the compiler nor the runtime system could prevent it.
Here is another example
that illustrates the potential danger of ignoring the warning issued regarding array construction in conjunction with variable argument lists.
static <T> T[] method_1(T t1, T t2) {
return method_2(t1, t2); // unchecked warning
}
static <T> T[] method_2( T... args) {
return args;
}
public static void main(String... args) {
String[] strings = method_1("bad", "karma"); // ClassCastException
}
warning: [unchecked] unchecked generic array creation of type T[] for varargs parameter
return method_2(t1, t2);
As in the previous example, the array's component type is non-reifiable and due to type erasure the compiler does not create a T[] , but an Object[] instead. Here is what the compiler generates:
Example (same a above, after translation by type erasure):
public final class Test {
static Object[] method_1( Object t1, Object t2) {
return method_2( new Object[] {t1, t2} ); // unchecked warning
}
static Object[] method_2( Object[] args) {
return args;
}
public static void main(String[] args) {
String[] strings = (String[]) method_1("bad", "karma"); // ClassCastException
}
}
The unchecked warning is issued to alert you to the potential risk of type safety violations and unexpected ClassCastExceptions
In the example, you would observe a ClassCastException
in the main()
method where two strings are passed to the first method. At runtime, the two strings are stuffed into an Object[]
; note, not a String[]
.
The second method accepts the Object[] as an argument
, because after type erasure Object[]
is its declared parameter type. Consequently, the second method returns an Object[] , not a String[]
, which is passed along as the first method's return value. Eventually, the compiler-generated cast in the main()
method fails, because the return value of the first method is an Object[] and no String[]
Conclusion
It is probably best to avoid providing objects of non-reifiable types where a variable argument list is expected. You will always receive an unchecked warning and unless you know exactly what the invoked method does you can never be sure that the invocation is type-safe.
You have to use a Class instance of T to cast since the generic type erasure during compilation
public class Test {
public static void m1() {
m3(m4("1", String.class));
}
public static void m2() {
m3(m4("1", String.class), m4("2", String.class));
}
public static void m3(final Object... str) {
for (Object o : str) {
System.out.println(o);
}
}
public static <T> T m4(final Object s, Class<T> clazz) {
return clazz.cast(s);
}
public static void main(String[] args) {
m1();
m2();
}
}
$java Test
1
1
2
Varargs and Generics don't mix to well in Java. This is because
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With