Appearance
Generics and Reflection
Some parts of Java's reflection API are also generics. For example, Class<T>
is generic:
java
// compile warning:
Class clazz = String.class;
String str = (String) clazz.newInstance();
// no warning:
Class<String> clazz = String.class;
String str = clazz.newInstance();
The getSuperclass()
method of Class
returns a Class
type of Class<? super T>
:
java
Class<? super String> sup = String.class.getSuperclass();
The Constructor<T>
is also generic:
java
Class<Integer> clazz = Integer.class;
Constructor<Integer> cons = clazz.getConstructor(int.class);
Integer i = cons.newInstance(123);
You can declare arrays with generics, but you cannot create them using the new
operator:
java
Pair<String>[] ps = null; // ok
Pair<String>[] ps = new Pair<String>[2]; // compile error!
You must use a cast to create arrays with generics:
java
@SuppressWarnings("unchecked")
Pair<String>[] ps = (Pair<String>[]) new Pair[2];
Be particularly cautious when using generic arrays, as arrays do not actually maintain generics at runtime. The compiler can enforce checks on the variable ps
because it is a generic array, but it does not check the variable arr
since it is not a generic array. Since these two variables actually point to the same array, manipulating arr
could cause an error when retrieving elements from ps
. The following code demonstrates unsafe usage of a generic array:
java
Pair[] arr = new Pair[2];
Pair<String>[] ps = (Pair<String>[]) arr;
ps[0] = new Pair<String>("a", "b");
arr[1] = new Pair<Integer>(1, 2);
// ClassCastException:
Pair<String> p = ps[1];
String s = p.getFirst();
To use generic arrays safely, you must eliminate the reference to arr
:
java
@SuppressWarnings("unchecked")
Pair<String>[] ps = (Pair<String>[]) new Pair[2];
In the above code, without access to the original array reference, you can only operate on the generic array ps
, making this operation safe.
Generic arrays effectively demonstrate the compiler's type erasure:
java
Pair[] arr = new Pair[2];
Pair<String>[] ps = (Pair<String>[]) arr;
System.out.println(ps.getClass() == Pair[].class); // true
String s1 = (String) arr[0].getFirst();
String s2 = ps[0].getFirst();
Therefore, we cannot directly create a generic array T[]
because, after erasure, the code becomes Object[]
:
java
// compile error:
public class Abc<T> {
T[] createArray() {
return new T[5];
}
}
You must use Class<T>
to create generic arrays:
java
T[] createArray(Class<T> cls) {
return (T[]) Array.newInstance(cls, 5);
}
You can also create generic arrays T[]
using varargs:
java
public class ArrayHelper {
@SafeVarargs
static <T> T[] asArray(T... objs) {
return objs;
}
}
String[] ss = ArrayHelper.asArray("a", "b", "c");
Integer[] ns = ArrayHelper.asArray(1, 2, 3);
Caution with Generic Varargs
In the example above, it seems safe to create a generic array using:
java
static <T> T[] asArray(T... objs) {
return objs;
}
However, this method is very dangerous. The following code comes from an example in "Effective Java":
java
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
String[] arr = asArray("one", "two", "three");
System.out.println(Arrays.toString(arr));
// ClassCastException:
String[] firstTwo = pickTwo("one", "two", "three");
System.out.println(Arrays.toString(firstTwo));
}
static <K> K[] pickTwo(K k1, K k2, K k3) {
return asArray(k1, k2);
}
static <T> T[] asArray(T... objs) {
return objs;
}
}
Directly calling asArray(T...)
seems fine, but returning a generic array from another method leads to ClassCastException
. This is due to type erasure; within the pickTwo()
method, the compiler cannot detect the correct type of K[]
, so it returns Object[]
.
If you look closely, you will notice that the compiler issues warnings for all varargs of generic types, and you should only suppress these warnings with @SafeVarargs
if you are absolutely certain there are no issues.
Note
If you create a generic array within a method, it's best not to return it for external use.
For a more detailed explanation, refer to "Effective Java" in "Item 32: Combine generics and varargs judiciously."
Summary
- Some reflection APIs are generics, such as
Class<T>
andConstructor<T>
. - You can declare arrays with generics but cannot directly create them; you must use a cast.
- You can create
T[]
arrays usingArray.newInstance(Class<T>, int)
with a cast. - Be especially cautious when using generics and varargs together.