泛型
1.
class Test<T> //也可以有多个泛型类型如 class Test<T,U>...以逗号分隔
{
private T t; //T代表某一个类型
...
public static <U> U getSomthing() //泛型方法,可以在泛型类中,也可以在普通类中。
{
...
}
}
调用时:new Test<String>().<String>getSomthing(); //第2个对方法的泛型具体化时要放在方法名的前面。在编译器可以根据参数自动确定方法使用的U代表哪种类型时,方法名前的<String>可以省略
2.限定泛型的类型。
例如:public static <T extends Comparable> T min(T[] a){...} //只有实现了Comparable接口的类才可以替换T
也可以限定多个类型:<T extends Comparable & Serializable>
使用&分隔
注意:无论限定为接口还是类,一律使用extends。另外,限定的最多只能有一个类,可以有多个接口,并且,类在<>中的顺序放在第一个。
3.泛型代码和虚拟机
擦除:将具体类型替换为Object类型(没有限定泛型类型时)或者是限定泛型类型的第一个类型,例如2中的第2个代码,将会替换为Comparable类型。
擦除之后的类型成为原始类型。
4.使用泛型时需要考虑的限制
(1)不能用基本类型实例化类型参数。例如,不能<double>
,可以<Double>
(2)运行时类型都会被擦除为原始类型。所以:
[1]不能使用 a instanceof Pair<String>
,只能够使用 a instanceof Pair 或者 a instanceof Pair<?>
。也就是说,instanceof对于泛型类只能够判断a是否是任意类型的一个Pair对象。
[2]对于泛型类,getClass方法也只会返回原始类型。如:Pair<String> stringPair=...; Pair<Employee> employeePair=...;
此时,stringPair.getClass()==employeePair.getClass()
会返回true,因为getClass方法返回的都是Pair.class
[3]也不能进行强制类型转换,例如 Pair<String> p = (Pair<String>) a
(3)不能实例化参数化类型的数组。例如:Pair<String>[] table = new Pair<String>[10];
注意:只是不能实例化(new Pair<String>[10]
),但是可以声明(Pair<String>[] table
)
(4)对于参数可变的方法,可以向方法中传入多个泛型类型的实例(实际上会将这几个泛型实例放在一个数组中,但是这里限制有所放松,只会给出警告),例如:
public static <T> void addAll(Collection<T> coll, T... t) //这里的t实际上是一个数组,会给出警告。但是可以使用@SuppressWarnings(unchecked)抑制警告或者@SafeVarargs接触限制。
{
...
}
但是,会隐藏着危险:
Pair<String>[] table = ...;
Object[] objarray = table;
objarray[0] = new Pair<Employee>(); //将一个Pair<Employee>类型的对象插入到Pair<String>类型的数组中。能够顺利运行并且没有异常,但是在使用table[0]时,会在别处得到一个异常。
(5)不能实例化类型变量,像 new T(…), new T[…], T.class都不可以使用
(6)不能在静态域或者静态方法中引用类型变量。例如 private static T singleInstance;(不允许有T)
(7)既不能抛出也不能捕获泛型类对象,甚至泛型类 扩展 Throwable类都是不合法的。
例如:
public class Problem<T> extends Exception{...} //这样不可以
但是下面这样是可以的:
public static <T extends Throwable> void doWork(T t) throws T {} //合法
另外,Java异常处理的原则是必须为所有的已检查异常提供一个处理器,然而可以利用<T extends Throwable>
消除编译器对已检查异常的检查,对于如何去做,这里不详细描述。
(8)要想支持擦除的转换,就需要强制限制一个类或者类型变量不能同时成为两个接口类型的子类,而这两个接口是同一接口的不同参数化。例如,下面代码是非法的
class Calendar implements Comparable<Calendar>{...}
class GregorianCalendar extends Calendar implements Comparable<GregorianCalendar>{...} //Error
5.泛型类型的继承规则
(1)Employee是Manager的超类,然而Pair<Manager>
并不是Pair<Employee>
类的子类,他们之间没有任何关系。也不能用一个Pair<Employee>
变量引用一个Pair<Manager>
对象。
(2)永远可以将参数化类型转换为一个原始类型。例如,Pair<Employee>
是原始类型Pair的一个子类型。
Pair<Manager> managerBuddies = new Pair<>{ceo, cfo}; //new的对象中的<>可以像这样不写Manager,这样managerBuddies中的T也是自动变成Manager
Pair rawBuddies = managerBuddies; //完全可以
rawBuddies.setFirst(new File("...")); //编译器只会产生一个警告,但是运行时会抛出异常。
(3)泛型类可以扩展实现其他的泛型类。例如,ArrayList<T>
实现List<T>
接口。这意味着,一个ArrayList<Manager>
可以被转换为一个List<Manager>
6.通配符类型(带有子类型限定的通配符可以从泛型对象读取,带有超类型限定的通配符可以向泛型对象写入)
(1)通配符的子类型限定
Pair<Manager>
和Pair<Employee>
都是Pair<? extends Employee>
的子类型。因此,可以用Pair<? extends Employee>
类型的变量引用Pair<Manager>
类型的对象。例如,
Pair<? extends Employee> p = new Pair<Manager>(...); //没毛病
Employee first = p.getFirst(); //没毛病
p.setFirst(...); //有毛病,p的setFirst方法不允许接收任何类型的参数,因为?无法与任何类型匹配,不知道到底是哪个类型。
(2)通配符的超类型的限定
[1]像Pair<? super Manager>
通配符限制为Manager的超类型(不包括Manager)。此时Pair<Employee>和Pair<Object>
都是Pair<? super Manager>
的 子类。
[2]可以使用set方法,但是不可以使用get方法,也就是说与(1)相反:
Pair<? super Manager> p = new Pair<Employee>(...); //没毛病
Employee first = p.getFirst(); //有毛病
p.setFirst(...); //没毛病
(3)可以使用<T extends Comparable<? super T> >
替换<T extends Comparable<T> >
, 因为后一种情况有时会出问题,例如当T为GregorianCalandar时,由于GregorianCalendar并亲自没有实现compareTo方法,而是从Calendar继承来的。所以GregorianCalendar实现的是Comparable<Calendar>
,而不是Comparable<GregorianCalendar>
,所以会出问题。
(4)无限定通配符Pair<?>
, 使用getFirst方法只能返回给一个Object变量,setFirst方法不能被调用。(但是可以setFirst(null))
*没看懂到底有什么用
(5)通配符的捕获
如交换Pair对象的两个数据成员first和second
public static void swap(Pair<?> p) //假设要求必须用Pair<?>类型的参数
{
swapHelper(p); //这里swapHelper中的Pair<T>就捕获到了swap函数中的Pair<?>类型的对象
}
public static <T> void swapHelper(Pair<T> p)
{
T t = p.getFirst();
p.setFirst(p.getSecond());
p.setSecond(t);
}