泛型对于老代码的支持
Java的泛型设计成类型擦除的目的,很大一部分是为了兼容老老代码。如下的一段代码:
void setLabelTable(Dictionary table)
table的类型是非泛型的Dictionary,但是我们可以传入泛型的Dictionary:
Dictionary<Integer, Component> labelTable = new Hashtable<>();
labelTable.put(0, new JLabel(new ImageIcon("nine.gif")));
labelTable.put(20, new JLabel(new ImageIcon("ten.gif")));
...
slider.setLabelTable(labelTable); // WARNING
注意,当向setLabelTable传入一个泛型的Dic,编译器会产生一个警告。
还有一种相反的情况,一个方法的返回类型是原始类型,但是可以分配给一个泛型类型持有:
Dictionary<Integer, Components> labelTable = slider.getLabelTable(); // WARNING
这种情况也会产生一个警告信息。
可以是用java注解来使这些警告消失掉:
@SuppressWarnings("unchecked")
Dictionary<Integer, Components> labelTable = slider.getLabelTable(); // No warning
或者注解在整个方法上面,使得方法内的所有警告消失:
@SuppressWarnings("unchecked")
public void configureSlider() { . . . }
泛型的使用限制
泛型的很多限制都是由于类型擦除机制带来的
泛型的类型参数不能是原始类型
Type Parameters Cannot Be Instantiated with Primitive Types
不能用Pair<double>,而只能使用Pair<Double>
运行时的类型查询与判断只能作用于非泛型类型,不支持泛型类的类型查询
Runtime Type Inquiry Only Works with Raw Types
由于Java虚拟机运行的代码都是没有泛型的,所以只能对非泛型类型进行类型查询与判断,如下代码是错误的:
if (a instanceof Pair<String>) // ERROR
if (a instanceof Pair<T>) // ERROR
只能对a instanceof Pair,不能判断泛型类。
下面这种情况会查询一个编译时的警告
Pair<String> p = (Pair<String>) a; // WARNING--can only test that a is a Pair
同样的,进行下面的判断,也只是对擦除后的类型进行的,与使用的什么类型参数无关:
Pair<String> stringPair = . . .;
Pair<Employee> employeePair = . . .;
if (stringPair.getClass() == employeePair.getClass()) // they are equal
上面的比较返回的是true,因为他们都属于擦除后的类型Pair.class
不能创建泛型类的数组
You Cannot Create Arrays of Parameterized Types
下面这种代码是错误的:
Pair<String>[] table = new Pair<String>[10]; // ERROR
在类型擦除后,table的类型实际上是Pair[],可以将其转换为Object[]类型:
Object[] objarray = table;
由于数组是可以记住它内部元素的类型的,并且当你试图存储一个类型不匹配的元素时,会抛出ArrayStoreException异常。
那么像下面的代码:
objarray[0] = new Pair<Employee>();
首先,进行类型擦除,那么就变成了objarray[0] = new Pair();
但是objarray记住的内部元素类型应该是Pair, 与Pair不符,那么就会抛出异常。
由于这种情况,Java不允许new一个泛型类型的数组。
如果需要存储泛型类对象的集合,直接使用ArrayList即可,既安全又高效。
ArrayList<Pair<String>>
可变参数使用泛型类型
像下面这种泛型类型作为可变参数:
public static <T> void addAll(Collection<T> coll, T... ts)
{
for (t : ts) coll.add(t);
}
由于可变参数实际上是语法糖,就是一个数组,那么ts就是一个T[]数组,当我们使用这个addAll方法,如下:
Collection<Pair<String>> table = ...;
Pair<String> pair1 = ...;
Pair<String> pair2 = ...;
addAll(table, pair1, pair2);
Java会将形参ts转换为Pair泛型类型数组:
Pair<String>[]
根据上一个限制,Java是不允许创建泛型类型的数组的。但是,在这里Java允许创建这种泛型类型数组。
这样传入泛型对象时,Java会报出警告信息,可以使用注解放在包含调用addAll方法的方法来去掉这种警告:
@SuppressWarnings("unchecked")
在Java7以后,可以使用新的注解@SafeVarargs来注解addAll方法本身,来去掉这种警告:
@SafeVarargs
public static <T> void addAll(Collection<T> coll, T... ts)
不能初始化泛型类类型参数
不能像如下代码使用来直接new一个类型参数:
new T(...), new T[...], or T.class.
如下在Pair< T >中直接new一个类型参数是非法的:
public Pair() { first = new T(); second = new T(); } // ERROR
因为类型擦除,会将T擦除为Object,那么上面的代码直接就是new Object(), 显然不是我们想要的。
workaround的方法是使用反射机制来new一个泛型类型的对象,使用时,需要多传入一个Class< T > 对象,如下:
public static <T> Pair<T> makePair(Class<T> cl)
{
try
{
return new Pair<>(cl.newInstance(), cl.newInstance())
}
catch (Exception ex)
{
return null;
}
}
上面的方法需要使用方传入一个泛型类型T的Class对象,然后使用Class的newInstance方法产生泛型类型的实例。
注意,Class自身就是泛型的,比如,String.class就是一个Class< String >的实例。
同样也不允许直接new一个泛型类型数组,如下代码:
// ERROR
public static <T extends Comparable> T[] minmax(T[] a)
{
T[] mm = new T[2];
. . .
}
上面的代码,擦除到边界后变为了Comparable[] mm = new Comparable[2]; 这也不是我们想要的,如果你只想将泛型类数组当成一个内部使用的字段,那么可以想下面的ArrayList< E> 一样直接内部使用Object[] 即可:
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)
{
// no cast needed
elements[n] = e;
}
}
当然,如果需要在一个方法中返回泛型数组的话,下面的代码是错误的:
public static <T extends Comparable> T[] minmax(T... a)
{
Object[] mm = new Object[2];
...
return (T[]) mm; // compiles with warning
}
因为编译器会擦除到Comparable,返回的数组就不是你想要的泛型类型数组了,解决方法如下:
public static <T extends Comparable> T[] minmax(T... a)
{
T[] mm = (T[]) Array.newInstance(a.getClass().getComponentType(), 2);
...
}
使用Array的newIntance方法来生成数组。
类的静态字段不能使用泛型类
下面的静态字段不能使泛型类:
public class Singleton<T>
{
private static T singleInstance; // ERROR
public static T getSingleInstance() // ERROR
{
if (singleInstance == null)
{
construct new instance of T
return singleInstance;
}
}
}
因为类型擦除,singleInstance不管传入什么,都是一个Object类型,所以没有意义。
不能抛出泛型类异常,也不能捕获泛型类异常的实例
下面的代码,直接扩展Exception是不允许的:
public class Problem<T> extends Exception { /* . . . */ } // ERROR-- can't extend Throwable
下面的代码,也不能catch一个泛型类型的异常:
public static <T extends Throwable> void doWork(Class<T> t)
{
try
{
do work
}
catch (T e) // ERROR--can't catch type variable
{
Logger.global.info(...)
}
}
但是泛型类型参数作为方法名后的throws T是允许的,像下面的代码:
public static <T extends Throwable> void doWork(T t) throws T // OK
{
try
{
do work
}
catch (Throwable realCause)
{
t.initCause(realCause);
throw t;
}
}
注意泛型参数被擦除后带来的冲突
如下代码:
public class Pair<T>
{
public boolean equals(T value)
{
return first.equals(value) && second.equals(value);
}
...
}
当我们定义了一个Pair< String>泛型类,如果没有擦除机制,那么Pair< String>类里面会有两个如下的equals方法,一个定义在此类中,另一个定义在隐式继承类Object中:
boolean equals(String) // defined in Pair<T>
boolean equals(Object) // inherited from Object
但是,在擦除掉Pair< String>中的String后,实际上下面这个equals方法:
boolean equals(T)
擦除后变为了下面这样:
boolean equals(Object)
那么这个equals方法就覆盖掉了Object类中的equals方法,也就发生了冲突。解决方法很简单,就是重命名Pair< T>中的equals方法。
不允许一个类继承自两个只是泛型类参数不同的类
像下面的GregorianCalendar类,间接继承自Comparable和Comparable两个只是泛型类参数不同的同一个接口是不允许的:
class Calendar implements Comparable<Calendar> { . . . }
class GregorianCalendar extends Calendar implements Comparable<GregorianCalendar>
{ . . . } // ERROR
泛型类型的继承规则
如果Employee是父类,Manager继承自Employee,那么这两个类作为同一个泛型类的类型参数时,这两个泛型类没有任何关系
注意泛型类与Java数组之间的一个重要的区别,可以将一个Manager[]数组赋给一个类型为Employee[]数组的变量:
Manager[] managerBuddies = {ceo, cfo};
Employee[] employeeBuddies = managerBuddies; //OK
然而,数组带有特别的保护,如果试图将一个低级别的Employee存储到employeeBuddies[0], 虚拟机将会抛出ArrayStoreException异常。
你总是可以将一个泛型类型转换为它的原始类型,例如,Pair< Employee>是原始类型
Pair的一个子类,这种转换能力是用来兼容Java老代码的。
最后,泛型类可以继承或者实现其他泛型类,在这一点上,它与普通类没有区别。例如,ArrayList< T>实现了接口List< T>,那么ArrayList< Manager>就可以转换为List< Manager>,但是ArrayList< Manager>与ArrayList< Employee>和List< Employee>无关,不能转换