约束与局限性
-
不能用primitive作为范型类型
类型擦除后,默认限定类是Object
,而primitive的基类不是Object
。 -
运行时类型查询:无范型信息(虚拟机上没有范型类型)
if (a instanceof Pair<String>)
判断类型=>ERROR
Pair<String> p = (Pair<String>)a
类型转换=>WARNING -
不能创建参数化类型的数组:因为参数擦除,和数组类型检查
Pair<String>[] a = new Pair<String>[10]
//ERRORJava中的数组是有类型检查的,如果
add
的类型有误,会抛出ArrayStoreException
。Pair<String>
和Pair<Employee>
在类型擦除后,都是Pair。因此数组Pair<String>[]
无法检查非法类型的成员Pair<Employee>
。- 注意:只是不能初始化这样的数组,声明范型的数组变量是允许的。
-
注意:可以用后面提到的通配类型的数组来进行范型数组的初始化,然后进行类型转换。
Pair<String>[] table = (Pair<String>[]) new Pair<?>[10];
-
注意:要使用范型对象的集合,只有使用相应的容器类才是唯一安全有效的方法:
ArrayList<Pair<String>>
-
Varargs警告
不定长参数实际上是一个数组,下面的不定长参数列表会在系统中建立一个范型数组:public static <T> void addAll(Collection<T> c, T... ts) Collection<Pair<String>> table = ...; addAll(c, new Pair<String>(), new Pair<String>);
对于这种情况,Java放松了限制,只会给出一个警告,而不是Error。这个警告可以由
@SafeVarargs
来消除。 -
不能实例化类型变量
由于类型擦除的原因:new T()
这种代码肯定是不允许的。//错误的用法 public Pair() {first = new T(); second = new T();} // Java8: Lambda表达式 - 构造器表达式 // Supplier接口是一个函数式接口,无参数且返回T。 public static <T> Pair<T> makePair(Supplier<T> constr) { return new Pair<>(constr.get(), constr.get()); } //调用 Pair<String> p = Pair.makePair(String::new); // Java8以前的方法:通过反射newInstance() public static <T> Pair<T> makePair(Class<T> cl) { try { return new Pair<>(cl.newInstance(), cl.newInstance()); } catch (Exception ex) { return null; } } //调用: Pair<String> p = Pair.makePair(String.class);
-
不能构造参数类型数组
T[]
//由于类型擦除,mm总会是: Comparable[2] public static <T extends Comparable> T[] minmax(T[] a) { T[] mm = new T[2]; //ERROR ... } //Java8: Lambda表达式 - 构造器表达式 public static <T extends Comparable> T[] minmax(IntFunction<T[]> constr, T... a) { T[] mm = constr.apply(2); } //调用 String[] ss = minmax(String[]::new, "Str1", "Str2", "Str3"); // Java8以前的方法:通过反射newInstance() public static <T extends Comparable> T[] minmax(T... a) { T[] mm = Array.newInstance(a.getClass().getComponentType(), 2); ... } //调用: Pair<String> p = Pair.makePair(String.class);
ArrayList<T>.toArray()在类型擦除后,无法通过参数获取T的类型,因此只能提供两种方案:
- Object[] toArray():传统方法
- T[] toArray(T[] a):a是目标数组,足够大就装进去,不足够大也可以获取T的类型,从而构造个新的。
-
不能在静态域或静态方法中引用类型变量
由于类型擦除,下面的代码会导致ERRORpublic class Singleton<T> { private static T singleton; //ERROR public static T getSingleton() { //ERROR if (singleton == null) { ... } return singleton; } }
-
不能抛出、捕获T,不能用范型类扩展
Throwable
-
thorw T;
//ERROR -
catch (T e);
//ERROR -
P<T> extends Throwable
//ERROR -
函数声明抛出T异常:OK
public static <T extends Throwable> void doWork(T t) throws T { ... }
-
-
Trick:不检查checked异常
Java异常处理的一个基本原则是:必须为所有checked异常提供一个处理器。// 定义一个语句块,这个语句块的body在一个单独的线程中运行 public abstract class Block { public Thread toThread() { return new Thread() { public void run() { //只会抛出unchecked异常,无需声明throws,外部无需catch try { body(); //body是abstract方法,由用户实现,可抛出异常 } catch (Throwable t) { //如果有异常,当作RuntimeException(属于unchecked异常)抛出 Block.<RuntimeException>throwAs(t); } } } } @SuppressWarnings("unchecked") public static <T extends Throwable> void throwAs(Throwable e) throws T { //由于类型擦除,虚拟机上的T实际上都是Throwable //但是编译器层面会认为抛出的类是RuntimeException,因而算作unchecked异常 throw (T) e; } public abstract void body() throws Exception; } // 调用 new Block() { public void body() throws Exception {...} }.toThread().start();
-
类型擦除可能引发的冲突
//冲突1: 类型擦除后,与Object.equals(Object)冲突 public class Pair<T> { public boolean equals(T value) {return ...} } //冲突2: Manager是Comparable<Manager>、Comparable<Employee>这两个接口类的子类 // 而且这两个接口类是相通接口的不同类型化,这导致内部生成桥2个如下的桥函数(X不同) // public int compareTo(Object other) {return compareTo((X) other);} class Employee implements Comparable<Employee> {...} class Manager implements Comparable<Manager> {...}
范型类型的继承规则
-
Manager
是Employee
的子类,但是Pair<Manager>
并不是Pair<Employee>
的子类。这种错觉可能是因为Java数组:Manager[]
可以赋给Employee[]
。 -
Pair<Manager>
和Pair<Employee>
都可以赋给RAW TYPE的Pair
,但是这样会失去范型的类型安全保护。 - 范型类型是可以
extends
和implements
的,如:ArrayList<T> implements List<T>。
但是请比较下面的例子:-
ArrayList<Manager>
和List<Manager>
-
ArrayList<Manager>
和ArrayList<Employee>
-
网友评论