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.
How to solve it?
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
Varags implemented by having an array of the respective type at runtime (array of Object in your case)
Arrays and Generics are just incompatible. You can't have an Array of String-Lists.
Related
Imagine this two sample codes:
public class TestCompile<T> {
private T[] array;
public static void main(String[] args) {
}
}
public class TestNoCompile<T> {
private T[] array = new T[5];
public static void main(String[] args) {
}
}
The first class TestCompile has no errors at compilation time and the second one TestNoCompile is not able to compile.
I understant why the second one doesnt compile since the arrays in Java are covariant and the type erasure is not compatible with that. But I cant understant why the first example compiles, why can I declare a generic array if then I cant initialize it?
On the other hand I cant unserstant this other example:
public class Example<T> {
private T[] array;
public static void main(String[] args) {
Example<Integer> example = new Example<>();
example.method(new Integer[5]);
}
public void method(T[] array) {
array[0] = 1; //This line doesnt compile.
}
Here it seems Im able to initialize a generic array in the method method(...) but then Im not able to store any value in it. Which is the explanation of this behaviour?
There's nothing wrong with the type T[] itself. It's perfectly fine to have a variable of type T[] and you can assign any value of type T[] to that variable fine, without any warnings. The question is how do you get a value of type T[].
I think you've answered your own question later when you showed that, for example, you can have a value of type T[] passed in from the outside, into a method (or a constructor) of your class. And in the caller's scope in your example, T is a concrete type (Integer), so the caller can create a T[] in its scope fine and pass it in.
As you have found, you can't create a value of type T[] (other than null) without a warning inside the class (where T is generic). This is because arrays know their component type at runtime (because arrays in Java check at runtime every element that is stored into the array is an instance of the component type), so to create an array object, you need to provide the component type of the array you want to create at runtime, and inside the class, you don't know what T is at runtime. So new T[5] is not a valid expression.
In Andreas's answer, they create an array of type Object[], and then cast it to T[], but this is basically lying to the compiler. Obviously, if T is any type other than Object, this cast is incorrect. For example, String[] foo = (String[]) new Object[5]; throws a class cast exception at runtime. However, T is erased to Object inside the class, so it does not immediately throw a class cast exception. You get an unchecked cast warning to warn you that you might not get an exception even if the cast is incorrect, so you may have a variable whose compile-time type is incompatible with its runtime type, and you may unexpectedly get a class cast exception somewhere else later. For example, if you have a method that returns the array to the outside of the class as type T[], and the place outside the class has a concrete type for T, it will cause a class cast exception where there is no cast:
public class Example<T> {
private T[] array = (T[]) new Object[5];
public T[] getArray() {
return array;
}
public static void main(String[] args) {
Example<Integer> example = new Example<>();
Integer[] foo = example.getArray(); // class cast exception
}
}
Your statement that you cannot store any value in the array is incorrect. You can store values in it, but you can only store values of type T. Inside the class, you don't know what T is, so where are you going to get a value of type T? You would either have to use null, or you have to get it from outside the class:
public class Example<T> {
private T[] array;
public Example(T[] a) {
array = a;
}
public void set(int i, T x) {
array[i] = x;
}
public static void main(String[] args) {
Example<Integer> example = new Example<>(new Integer[5]);
example.set(0, 1);
}
}
Because of type-erasure, a generic array becomes an Object[] at runtime, so you need to create it as such, cast it, and acknowledge that what you're doing is not safe:
#SuppressWarnings("unchecked")
private T[] array = (T[]) new Object[5];
As for the array[0] = 1 statement, the problem is that array is a T[], and that T can be anything, so the code isn't valid.
What is you changed the code in main as follows?
Example<String> example = new Example<>();
example.method(new String[5]);
The array[0] = 1 statement is now obviously not valid, and remember, declaring a Example<String> could easily be done elsewhere at the same time you have Example<Integer> in main.
The code in method must be value for all possible T's.
I am new to Java. As I known, It's basically the way that generics are implemented in Java via compiler trickery.
public Object doSomething(Object obj) {....}
public <T> T doSomething(T t) {....}
According to type erasion, the above two method is the same at runtime. The only different is the way we use this method, compiler will auto add type casting when we use generic method.
Foo newFoo = (Foo) my.doSomething(foo);
Similarly, when we use generic array in method, as the below shown:
public void <T> T[] f(T[] args){
return args;
}
public void <T> Object[] f(Object[] args){
return args;
}
I think the above two methods are same at runtime because of type erasion.
Integer[] a = {1, 2};
Integer[] b = test.f(a);
When I use this method, I think the generic method will throw an CaseException.
When we pass a to test.f(a), the JVM cast Integer[] to Object[].
And when we get the result from this method, the JVM will also cast Object[] to Integer[] and this cast will throw an CaseException. because array in java is support covariant but not contravariant.
As a result, the above code works in both compile and runtime. There must be something wrong about my understanding. But I can not find out. Can anyone help me? Thank you!
T stands for a concrete type and it is not substitute for Object. It carries the actual type with itself. So when you pass an Integer[] to that method it returns Integer[] and it works properly - the compiler knows the type.
With your second method when you have Object[] you get the behavior you expect - you can pass Integer[] to Object[] but you cannot do vice versa and you get the compiler error.
Working examples:
public static <T> T[] f1(T[] args){
return args; //That one works because we return T[]
}
public static <T> T[] f2(Object[] args){
return (T[])args; //That one also works because we return T[]
}
The next one doesn't work because we return Object[] which might be of type T but might be of another type - it doesn't compile
public static Object[] f2(Object[] args){
return args; //Object[] is not T[]
}
I have the following generic test class:
public class BrokenGenerics<T> {
private T[] genericTypeArray;
public BrokenGenerics(T... initArray) {
genericTypeArray = initArray;
}
public void setArray(T[] newArray) {
genericTypeArray = newArray;
}
public T get(int idx) {
return genericTypeArray[idx];
}
public Class getType() {
return genericTypeArray.getClass().getComponentType();
}
public static boolean breakThis(BrokenGenerics any) {
any.setArray(new B[]{new B(2)});
return false;
}
public static void main(String[] args) {
BrokenGenerics<A> aBreaker = new BrokenGenerics<A>(new A("1"));
System.out.println(aBreaker.get(0));
System.out.println(aBreaker.getType());
breakThis(aBreaker);
System.out.println(aBreaker.get(0));
System.out.println(aBreaker.getType());
}
private static class A {
public String val;
public A(String init) {
val = init;
}
#Override
public String toString() {
return "A value: " + val;
}
}
private static class B {
public int val;
public B(int init) {
val = init;
}
#Override
public String toString() {
return "B value: " + val;
}
}
}
When I run it, I get this output, and no errors:
A value: 1
class BrokenGenerics$A
B value: 2
class BrokenGenerics$B
Now, I understand why this compiles; it can't know at compile-time that breakThis is being passed a generic of a bad type. However, once it runs the line any.setArray(new B[]{new B(2)});, shouldn't it throw a ClassCastException (NOTE THAT IT DOES NOT! Try it yourself!) because I'm trying to pass a B[] to a method that expects an A[]? And after that, why does it allow me to get() back the B?
After Type Erasure, T will be turned into Object since you didn't specify a bound on T. So, there is no problem at runtime assigning any type of array to genericTypeArray, which is now of type Object[] or calling the function setArray(...), which now also accepts an argument of type Object[]. Also, your get(...) method will simply return an Object.
Trouble starts when you access elements in the array with a wrong type expectation, since this might lead to (implicit or explicit) illegal type casts, for example by assigning the value returned by get(...) to a variable of type A.
You can also get a run-time ClassCastException if you try to type-cast the array itself, but, in my experience, that is a case that tends to come up less often, although it can be very obscure to find or even understand if it does happen. You can find some examples below.
All generics-checking happens only at compile-time. And if you use raw types, these checks can not be performed rigorously, and thus the best the compiler can do is to issue a warning to let you know that you are giving up an opportunity for more meaningful checks by omitting the type argument.
Eclipse with its standard settings (and probably the java compiler with the correct flags) shows these warnings for your code:
"Class is a raw type" where you define getType() (somewhat unrelated to your question)
"BrokenGenerics is a raw type" where you define breakThis(...)
"Type safety: The method setArray(Object[]) belongs to the raw type
BrokenGenerics" where you call setArray(...) inside breakThis(...).
Examples for causing ClassCastException due to illegal type-cast of the array:
You can get ClassCastExceptions at runtime if you expose the array to the outside world (which can often be a dangerous thing to do, so I try to avoid it) by adding the following to BrokenGenerics<T>:
public T[] getArray() {
return genericTypeArray;
}
If you then change your main method to:
BrokenGenerics<A> aBreaker = new BrokenGenerics<A>(new A("1"));
A[] array = aBreaker.getArray();
System.out.println(array[0]);
System.out.println(aBreaker.getType());
breakThis(aBreaker);
array = aBreaker.getArray(); // ClassCastException here!
System.out.println(array[0]);
System.out.println(aBreaker.getType());
You get the ClassCastException at runtime at the indicated position due to a cast of the array itself rather than one of its elements.
The same thing can also happen if you set the variable genericTypeArray to protected and use it from code that subclasses your generic class with a fixed type argument:
private static class C extends BrokenGenerics<A> {
public C(A... initArray) {
super(initArray);
}
public void printFirst() {
A[] result = genericTypeArray; // ClassCastException here!
System.out.println(result[0]);
}
}
To trigger the exception, add the following to you main method:
C cBreaker = new C(new A("1"));
cBreaker.printFirst();
breakThis(cBreaker);
cBreaker.printFirst();
Imagine this case coming up in a bigger project... How on earth would you even begin to understand how that line of code could possible fail?!? :) Especially since the stack trace might be of very little help trying to find the breakThis(...) call that is actually responsible for the error.
For more in-depth example cases, you can take a look at some tests I did a little while back.
shouldn't it throw a ClassCastException because I'm trying to pass a B[] to a method that expects an A[]?
No. As this post explains, your invocation of setArray in
public static boolean breakThis(BrokenGenerics any) {
any.setArray(new B[]{new B(2)});
return false;
}
is done on a reference expression of the raw type BrokenGenerics. When interacting with raw types, all corresponding generic parameters are erased. So setArray is actually expecting a Object[]. A B[] is a Object[].
why does it allow me to get() back the B?
Assuming you're asking about this
System.out.println(aBreaker.get(0));
PrintStream#println(Object) expects an Object, not an A. As such, there is no reason for the compiler to insert a cast here. Since there is no cast, there is no ClassCastException.
If you had instead done
A a = aBreaker.get(0);
or had a method like
void println(A a) {}
...
println(aBreaker.get(0));
then these would cause ClassCastException. In other words, the compiler will insert a cast (checkcast) anywhere a type needs to be converted from a generic type parameter. That was not the case with PrintStream#println.
Similarly,
System.out.println(aBreaker.getType());
doesn't even involve the generic parameter declared in BrokenGenerics
public Class getType() {...}
and also returns a value of the raw type Class. The compiler has no reason to add a checkcast to A.
I have a simple generic method that creates a list of n elements and returns it:
import java.util.*;
class A {
public static <T> List<T> init(int n) {
List<T> l = new ArrayList<T>();
while (n --> 0) l.add(null);
return l;
}
public static void main(String[] args) {
List<String> x = init(5);
f(x);
}
public static void f(List<String> l) {
System.out.println("l: " + l);
}
}
It works as expected:
$ javac A.java && java A
l: [null, null, null, null, null]
But if I eliminate the extra variable:
import java.util.*;
class A {
public static <T> List<T> init(int n) {
List<T> l = new ArrayList<T>();
while (n --> 0) l.add(null);
return l;
}
public static void main(String[] args) {
f(init(5));
}
public static void f(List<String> l) {
System.out.println("l: " + l);
}
}
it no longer compiles
$ javac A.java && java A
A.java:9: f(java.util.List<java.lang.String>) in A cannot be applied to (java.util.List<java.lang.Object>)
f(init(5));
^
1 error
Why?
But yet this works:
import java.util.*;
class A {
public static <T> List<T> init(int n) {
List<T> l = new ArrayList<T>();
while (n --> 0) l.add(null);
return l;
}
public static <T> T id(T t) {
return t;
}
public static void main(String[] args) {
List<String> x = init(5);
f(id(x));
}
public static void f(List<String> l) {
System.out.println("l: " + l);
}
}
Why?
Java relies on inference to figure out what the type variables are in a situation where they are not explicitly defined.
In your first example:
List<String> x = init(5);
f(x);
The compiler infers that you are calling <String> init because x is a List<String>.
In your second example:
f(init(5));
The compiler cannot infer that you are calling <String> init because you are not explicitly telling it (via A. <String> init(5)) nor are you assigning it to an appropriate variable.
In your third example:
List<String> x = init(5);
f(id(x));
The compiler infers that you are calling <List<String>> id which returns a List<String> to f.
The compiler isn't too smart about generic inference. Unless you explicitly state what the type arguments are, either by using a variable or by directly passing them to the method, it will not be able to figure them out.
Here are the relevant sections of the JLS if you're curious about the specifics:
JLS §15.12.2.7
JLS §15.12.2.8
If you leave out the extra variable the compiler has no way to infer the type parameter T when calling init(5). It assumes T to be Object and hence the compiler error.
With the extra variable declared as a List<String> x the compiler infer T to be String.
First, the fix;
f(A.<String>init(5)); // compiles
Now, why: The original code compiled because java could infer the type due to being assigned to a typed variable. But inference doesn't work when being passed to a typed parameter.
The fix uses the syntax for explicitly specifying the type when calling a typed method.
f(init(5));
This calls f() with the argument directly received from init(). However, init() now returns a List of T. With T not specified Java just uses Object because it is the base class for all objects. List<Object> is not List<String>, so the method signature and the parameters don't match.
List<String> x = init(5);
f(x);
Here you are putting the List<Object> to a variable of the type List<String>. This converts it, because String is of course a subclass of Object. Also the conversion succeeds because null can also be converted to String as well as any other class. Then x's type matches the method signature.
List<String> x = init(5);
f(id(x));
This is basically the same thing. Now, because x has the type List<String>, id()'s type T is String. This way the return value of id() is also List<String>, which matches the signature.
In both the first and the third example, you stated that you are talking about a List<String>, yet on the second one you said f(init(5)), which could be a List<Integer>. I'm not 100% that's the only reason, but check it out :)
I got this one from a google I/O puzzler talk given by Joshua Bloch. Here's the code
public class Glommer<T> {
String glom(Collection<?> obj){
String result = "";
for(Object o : obj){
result += o;
}
return result;
}
int glom(List<Integer> ints){
int result = 0;
for(int i : ints){
result += i;
}
return result;
}
public static void main(String args[]){
List<String> strings = Arrays.asList("1", "2", "3");
System.out.println(new Glommer().glom(strings));
}
this main method throws an exception because new Glommer is a raw type and hence all the generics in Glommer is erased, so it ends up calling int glom(List<Integer> ints) rather than String glom(Collection<?> obj).
My question is, even if I called glom() as new Glommer<Integer>().glom(strings) shouldn't it call the int glom(List<Integer> ints) method since due to type erasure, this method is effectively int glom(List ints) and strings is of type List not Collection?
The called method is defined at compilation time, not at runtime.
If you add a parameter to your constructor call, the compiler will have enough information to know that it has to call the first method. Otherwise, it's just as if generics didn't exist. In both case, the called method will always stay the same at runtime.
EDIT Some people seem to doubt, so here's another example:
public class Test {
private static void test(Object object) {
System.out.println("Object method");
}
private static void test(Integer integer) {
System.out.println("Integer method");
}
public static void main(String[] args) {
Object object = Integer.valueOf(0);
test(object);
}
}
The result is:
Object method
You pass an Integer to your method, but all that the compiler knows at compile time is that it's an object. The jvm doesn't automagically change the method call even though the Object is actually an Integer.
You can read more about Raw Types to understand it fully
Basically, raw types are for using legacy code, almost anything in a raw class will become raw itself, in this case those 2 methods.
So when it is raw there is a method that gets a List and one for Collection so its called the List one, if its not raw, the methods are not raw also and it will call the Collection one because it has the extra information
This is because when new Glommer() is called without generics the Generic<Type>(), all of type matches are removed from the class.
As strings variable is a List, if it does not have any generic <Type> then it will match the glom(List ints). The type checking doesn't get done till later.
When we create new Glommer<AnyType> the types are all left in place, so when we pass our strings variable it does type checking. The compiler can now check if it is a List<Integer>, which is not so it gets passed to the glom(Collection<?> obj) method.
Hope this helps, please ask for any clarification if you need!