在使用Java泛型时需要考虑一些限制, 大多数限制都是由于类型擦除所引起的。
1.不能使用基本类型实例化类型参数
类型参数 T不能取8中基本类型,需要的时候采用包装器类型,如果不能接受这样的替换时, 可以使用独立的类和方法来处理。
2.运行时的类型查询只会产生原始类型(以instanceof和getClass为例)
if(a instanceof Pair<String>) //error
if(a instanceof Pair<?>)
if(a instanceof Pair) //只能检查是否是一个Pair
Pair<String> stringPair = ....;
Pair<Employee> empPair = ...;
if(stringPair.getClass() == empPair.getClass())//equal,返回Pair.class
3.不能创建参数化类型的数组
不能创建泛型数组,想要收集泛型对象, 只有一种安全有效的方法, 例如:
pair<String>[] table = new Pair<String>[10]; //error
List list = new ArrayList<Pair<String>>();
String[] table = new String[10]; //普通数组
Object[] oArray = table; //赋给父类
table[1] = 1; //编译不通过,不能转换
Pair<String>[] table = new Pair<String>[10]; //不可以,假设可以
//擦除以后, table的类型是Pair[]。可以将其转换成Object[]
Object[] oArray = table;
table[0] = new Pair<Employee>(); //由于擦除可以通过数组存储检查,但仍然会导致类型错误, 所以不允许
note:只是不允许创建泛型数组, 而声明变量是合法的,只不过不能初始化。想要初始化可以采用下面的形式(数组的类型不可以是类型变量,而采用通配符的方式), 不过结果不安全, 因为随时可能产生类型错误。
Pair<String>[] table = (Pair<String>[]) new Pair<?>[10];//合法
4.Varargs警告
public static <T> void addAll(Collection<T> coll, T...ts) {
for(T t : ts) coll.add(t);
}
Collection<Pair<String>> table = new HashSet<>();
Pair<String> pair1 = new Pair<>();
Pair<String> pair2 = new Pair<>();
addAll(table, pair1, pair2);
//code3
@SafeVarargs
public static <T> void addAll(Collection<T> coll, T... ts)
这里的参数ts事实上是一个装有提供参数的数组,这里放宽了规则。关于变长泛型参数的传入,有警告不过不会报错。可以对仅仅读取泛型数组元素的方法使用code3中的Annotation。
5.不能实例化类型变量
public Pair() { first = new T(); second = new T(); } // Error
因为类型擦除会把T改变成Object, 显而易见new Object()没有什么意义,所以不能实例化类型变量。在Java8中采用构造器表达式来解决这个问题,如下例所示:
Pair<String> p = Pair.makePair(String::new);
public static <T> Pair<T> makePair(Supplier<T> constr) {
return new Pair<>(constr.get(), constr.get());
}
6.不能构造一个泛型数组
public static <T extends Comparable> T[] minmax(T[] a) { T[] mm = new T[2]; . . . } // Error
类型擦除将总会使得这个方法构造一个数组Comparable[2].
事实上,如果想使用的这样一个数组只是被当做一个类的私有域来使用, 可以通过将这样的数组声明为Object[], 并在查询元素时使用强制类型转换。例如ArrayList<E>
可以这样实现:
public class ArrayList<E> {
private Object[] elements;
. . .
@SuppressWarnings("unchecked") public E get(int n) { return (E) elements[n]; }
public void set(int n, E e) { elements[n] = e; } // no cast needed
}
7.类型变量不能出现在泛型类的静态上下文中
public class Singleton<T> {
private static T singleInstance; // Error
public static T getSingleInstance() { // Error
if (singleInstance == null)
construct new instance of T
return singleInstance;
}
}
泛型类上的泛型类型在实例化时确定。 比如:
Singleton<String> singletonForString= new Singleton();
Singleton<Boolean> singletonForBoolean= new Singleton();
但静态成员是被该类的所有对象共享的,所以泛型类型不能应用到静态成员上。
8.不能抛出或捕获泛型类实例
Java里面既不能抛出也不能捕获泛型类异常。
1.泛型类不能extends Throwable。
public class Problem<T> extends Exception { /* . . . */ } // Error
2.不可以在catch中使用类型变量,例如:
public static <T extends Throwable> void doWork(Class<T> t) {
try {
System.out.println();
} catch (T e) {// Error--can't catch type variable
e.printStackTrace();
}
}
3.在异常规范中是可以中使用泛型的,例如:
public static <T extends Throwable> void doWork(T t) throws T {
try {
...;
} catch (Throwable realCause) {
t.initCause(realCause);
throw t;
}
}
9.注意类型擦除后的冲突
要注意到在类型擦除后的冲突,例如:
public class Pair<T> {
public boolean equals(T value) {
return first.equals(value) && second.equals(value);
}
. . .
}
对于Pair<String>
,就存在两个方法:
boolean equals(String) // defined in Pair<T>
boolean equals(Object) // inherited from Object
擦除后的boolean equals(T)
就是与Object.equals
冲突的 boolean equals(Object)
`
泛型规范的另一条规则是:
To support translation by erasure, we impose the restriction that a class or type variable may not at the same time be a subtype of two interface types which are different parameterizations of the same interface.例如:
class Employee implements Comparable<Employee> { . . . }
class Manager extends Employee implements Comparable<Manager> { . . .} // Error
Manager 对象这里就是不合法的。原因可能是在桥方法上存在冲突。一个类实现了Comparable接口就会有一下桥方法:
public int compareTo(Object other) { return compareTo((X) other); }
这里就会出现两个桥方法,可能出现冲突?待证!