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[]
}
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.
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.
T[] genericArray= (T[])(new Object[2]);
T has a constraint that it implements Comparable. The line above fails with an exception
" java.lang.ClassCastException: [Ljava.lang.Object;
cannot be cast to [Ljava.lang.Comparable;
How do I initialize a generic array where the T has constraints?
Object doesn't implements Comparable.
Instead of creating it as Object[] create it as Comparable[].
Related to your comment:
A variable can be declared of any type. When you create an array you are not creating objects inside the array, but you are only allocating the memory to store the references to the objects.
So as you can write:
Comparable x = "Pippo"; // Because String is Comparable
you can also write
Comparable[] x = new Comparable[1];
x[0] = "Pippo"; // Here you add a concrete String that is a
// Comparable type on the first position
You get this error because Object does not implement Comparable and thus Object[] is not a sub-type of Comparable[] (which, because of type erasure, is the runtime type of your genericArray).
The underlying problem is that you want to create a generic array. This is not possible in Java. The reason is, that unlike generics, the type of the elements of an array is known at runtime. If you write new T[], it is not known which type of array must be created.
You try to circumvent this by creating an array of some supertype. But this is also not correct (and you should get a warning if you do it). If you create a an array with new Comparable[size], you create a an array of Comparables, not an array of some subtype of Comparable. A T[] might be a String[] or a Long[], and String[] and Long[] are different types than Comparable[] (also at runtime).
To demonstrate the problem, consider the following program:
public class Foo<T extends Comparable> {
T[] createArray() {
return (T[])new Comparable[1];
}
public static void main(String... args) {
Foo<String> foo = new Foo<>();
String[] ss = foo.createArray(); // here
}
}
It might look perfectly okay at first sight, but when your run it, you get a ClassCastException, because in the marked line a Object[] is cast to a String[].
The solution is to use Class<T> objects as so-called type tokens. These let you store the type so that you can access it at run-time. Now you can create an array with the correct type by using Array.newInstance(Class<T>, int...). For example:
public class Foo<T extends Comparable> {
private Class<T> type;
public Foo(Class<T> type) {
this.type = type;
}
T[] createArray() {
return (T[])Array.newInstance(type, 1);
}
public static void main(String... args) {
Foo<String> foo = new Foo<>(String.class);
String[] ss = foo.createArray();
}
}
(You may still get a warning from the compiler, because newInstance's return type is Object. You can ignore it because the object it returns is an array of the correct type.)
Whilst I was working on a project involving Java 8's new streams, I noticed that when I called Stream#toArray() on a stream, it return an Object[] instead of a T[]. Surprised as I was, I started digging into the source code of Java 8 and couldn't find any reason why they didn't implement Object[] toArray(); as T[] toArray();. Is there any reasoning behind this, or is it just an (in)consistency?
EDIT 1:
I noticed in the answers that a lot of people said this would not be possible, but this code snippet compiles and return the expected result?
import java.util.Arrays;
public class Test<R> {
private Object[] items;
public Test(R[] items) {
this.items = items;
}
public R[] toArray() {
return (R[]) items;
}
public static void main(String[] args) {
Test<Integer> integerTest = new Test<>(new Integer[]{
1, 2, 3, 4
});
System.out.println(Arrays.toString(integerTest.toArray()));
}
}
Try:
String[] = IntStream.range(0, 10).mapToObj(Object::toString).toArray(String[]::new);
The no-arg toArray() method will just return an Object[], but if you pass an array factory (which can be conveniently represented as an array constructor reference), you can get whatever (compatible) type you like.
This is the same problem that List#toArray() has. Type erasure prevents us from knowing the type of array we should return. Consider the following
class Custom<T> {
private T t;
public Custom (T t) {this.t = t;}
public T[] toArray() {return (T[]) new Object[] {t};} // if not Object[], what type?
}
Custom<String> custom = new Custom("hey");
String[] arr = custom.toArray(); // fails
An Object[] is not a String[] and therefore cannot be assigned to one, regardless of the cast. The same idea applies to Stream and List. Use the overloaded toArray(..) method.
About the reason why toArray() returns Object[]: it is because of type erasure. Generic types lose their type parameters at runtime so Stream<Integer>, Stream<String> and Stream become the same types. Therefore there is no way to determine component type of array to create. Actually, one could analyze types of array's elements using reflection and then try to find their least upper bound, but this is too complicated and slow.
There is a way to get R[] array by using overloaded toArray(IntFunction<A[]> generator) method. This method gives the caller an opportunity to choose type of the array. See this SO question for code examples: How to Convert a Java 8 Stream to an Array?.
I've recently come across the java #SafeVarargs annotation. Googling for what makes a variadic function in Java unsafe left me rather confused (heap poisoning? erased types?), so I'd like to know a few things:
What makes a variadic Java function unsafe in the #SafeVarargs sense (preferably explained in the form of an in-depth example)?
Why is this annotation left to the discretion of the programmer? Isn't this something the compiler should be able to check?
Is there some standard one must adhere to in order to ensure his function is indeed varags safe? If not, what are the best practices to ensure it?
1) There are many examples on the Internet and on StackOverflow about the particular issue with generics and varargs. Basically, it's when you have a variable number of arguments of a type-parameter type:
<T> void foo(T... args);
In Java, varargs are a syntactic sugar that undergoes a simple "re-writing" at compile-time: a varargs parameter of type X... is converted into a parameter of type X[]; and every time a call is made to this varargs method, the compiler collects all of the "variable arguments" that goes in the varargs parameter, and creates an array just like new X[] { ...(arguments go here)... }.
This works well when the varargs type is concrete like String.... When it's a type variable like T..., it also works when T is known to be a concrete type for that call. e.g. if the method above were part of a class Foo<T>, and you have a Foo<String> reference, then calling foo on it would be okay because we know T is String at that point in the code.
However, it does not work when the "value" of T is another type parameter. In Java, it is impossible to create an array of a type-parameter component type (new T[] { ... }). So Java instead uses new Object[] { ... } (here Object is the upper bound of T; if there upper bound were something different, it would be that instead of Object), and then gives you a compiler warning.
So what is wrong with creating new Object[] instead of new T[] or whatever? Well, arrays in Java know their component type at runtime. Thus, the passed array object will have the wrong component type at runtime.
For probably the most common use of varargs, simply to iterate over the elements, this is no problem (you don't care about the runtime type of the array), so this is safe:
#SafeVarargs
final <T> void foo(T... args) {
for (T x : args) {
// do stuff with x
}
}
However, for anything that depends on the runtime component type of the passed array, it will not be safe. Here is a simple example of something that is unsafe and crashes:
class UnSafeVarargs
{
static <T> T[] asArray(T... args) {
return args;
}
static <T> T[] arrayOfTwo(T a, T b) {
return asArray(a, b);
}
public static void main(String[] args) {
String[] bar = arrayOfTwo("hi", "mom");
}
}
The problem here is that we depend on the type of args to be T[] in order to return it as T[]. But actually the type of the argument at runtime is not an instance of T[].
3) If your method has an argument of type T... (where T is any type parameter), then:
Safe: If your method only depends on the fact that the elements of the array are instances of T
Unsafe: If it depends on the fact that the array is an instance of T[]
Things that depend on the runtime type of the array include: returning it as type T[], passing it as an argument to a parameter of type T[], getting the array type using .getClass(), passing it to methods that depend on the runtime type of the array, like List.toArray() and Arrays.copyOf(), etc.
2) The distinction I mentioned above is too complicated to be easily distinguished automatically.
For best practices, consider this.
If you have this:
public <T> void doSomething(A a, B b, T... manyTs) {
// Your code here
}
Change it to this:
public <T> void doSomething(A a, B b, T... manyTs) {
doSomething(a, b, Arrays.asList(manyTs));
}
private <T> void doSomething(A a, B b, List<T> manyTs) {
// Your code here
}
I've found I usually only add varargs to make it more convenient for my callers. It would almost always be more convenient for my internal implementation to use a List<>. So to piggy-back on Arrays.asList() and ensure there's no way I can introduce Heap Pollution, this is what I do.
I know this only answers your #3. newacct has given a great answer for #1 and #2 above, and I don't have enough reputation to just leave this as a comment. :P
#SafeVarargs is used to indicate that methods will not cause heap pollution.
Heap pollution is when we mix different parameterized types in generic array.
For example:
public static <T> T[] unsafe(T... elements) {
return elements;
}
Object [] listOfItems = unsafe("some value", 34, new ArrayList<>());
String stringValue = (String) listOfItems[0]; // some value
String intValue = (String) listOfItems[1]; // ClassCastException
As you can see, such implementation could easily cause ClassCastException if we don't guess with the type.