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.