使用泛型
更易读,更安全
泛型类
引入一个类型变量,使用<>括起来,放在类名后面
public class Pair<T,U>{...}
类型变量可用于指定方法的返回类型和参数类型、字段和局部变量的类型
常用的类型变量表示法:E(集合的元素类型)、K / V(分别表示表的键和值的类型)、T / U / S(任意类型)
使用具体的类型替代类型变量,来实例化泛型类型
Pair<int,int>
泛型方法
可以在普通类中定义一个泛型方法
类型变量放在修饰符的后面,并放在返回类型的前面
public static <T> T getMiddle(T... a)
当调用泛型方法时,使用具体的类型替代类型变量,并放在方法名前面
若编译器可以推断出具体类型,甚至可以在调用中省略类型参数
类型变量的限定
使用extends关键字来限定类型变量是某个类的子类或实现了某个接口,
一个类型变量可以有多个限定,使用&分隔限定,而使用,分隔类型变量
但多个限定中,最多只有一个限定是类,且这个限定是限定列表中的第一个
public static <T extends Comparable & Serializable> T min(T[] a){...}
泛型代码和虚拟机
虚拟机中没有泛型类型对象,所有对象都属于普通类
类型擦除
泛型类型都会被自动提供一个原始类型,即去掉类型参数的泛型类型名
作为方法的返回类型和参数类型、字段和局部变量类型的类型变量会被擦除,替换为其限定类型,而无限定的替换为Object
转换泛型表达式
在一个表达式中,调用将类型变量作为返回类型的方法,或访问泛型字段,擦除类型变量后,编译器会在字节码中插入强制类型转换
转换泛型方法
普通类中的泛型方法也会有类型擦除
编译器生成一个桥方法用于保持多态性
public void setSecond (Object second) {setSecond ((LocalDate) second);}
public LocalDate getSecond() {return (LocalDate) super.getSecond();}
在桥方法中,对被擦除类型的参数进行强制类型转换,然后重载解析,调用匹配的方法
在桥方法中,对被擦除返回类型的方法的返回值进行强制类型转换,并作为新的返回值
调用遗留代码
将一个泛型对象赋给原始类型的变量,或由一个遗留类得到一个原始类型的对象,将它赋给一个泛型变量,将会得到警告
使用注解消除这个警告
@SupressWarnings("Unchecked")
Dictionary<Integer,Components> labelTable=slider.getlabelTable();
泛型类型的继承规则
对于原始类型相同、类型变量不同的泛型类,不管类型变量之间是否存在关系,对应泛型类之间没有任何关系
参数类型是原始类型的一个子类型,可以将参数类型转换为原始类型,但会抛出ClassCastException异常
泛型类可以扩展或实现其他泛型类
限制与局限性
大部分限制都是由于类型擦除
不能用基本类型实例化
擦除后的类型仍是一个类,而不是基本类型,无法储存基本类型的值
运行时查询只适用于原始类型
虚拟机中只对原始类型进行操作,instanceof操作符、强制类型转换都不能使用泛型类型,getClass方法也只能返回原始类型
不能创建参数化类型的数组
可以声明参数化类型数组的变量,但不能创建参数化类型的数组来初始化这个变量
Varags警告
向参数个数可变的方法传递泛型类型实例时,Java虚拟机必须建立一个参数化类型的数组,从而违背了规则
但规则有所放松,只对得到一个警告而不是错误,可以确保安全的前提下使用注解
可以为包含参数长度可变方法调用的方法使用注解@SupressWarnings(“Unchecked”)
或直接对参数长度可变方法使用@SafeVarags,但只能用于static、final、private的方法
不能实例化类型变量
不能使用new操作符创建类型变量T的对象
最好的方法是提供一个构造器引用
public static <T> Pair<T> makePair(Supplier<T> constr)
{
return new Pair<>(constr.get(),constr.gte());
}
Pair<String> p = Pair.makePair(String::new);
传统的方法是使用反射
public static <T> Pair<T> makePair(Class<T> cl)
{
try{
return new Pair<>(cl.getConstructor().newInstance(),cl.getConstructor().newInstance());
}
catch (Exception e){return null;}
}
Pair<String> p = Pair.makePair(String.Class);
不能构造泛型数组?
不能使用new操作符创建元素类型为类型变量T的数组
可以将数组的元素类型声明为擦除的类型,并在使用强制类型转换
private Object[] elements;
...
@SupressWarnings("Unchecked")
public E get(int i) {return (E) elements[i];}
private E[] elements;
...
public ArrayList() {elements = (E[]) new Object[10];}
强制类型转换E[ ]是一个假象,而类型擦除使其无法察觉
与实例化类型变量的方法一样,最好提供一个构造器引用
或是使用反射
泛型类的静态上下文中类型变量无效
不能在静态字段或方法中引用类型变量
不能抛出或捕获泛型类的实例
泛型类不能扩展Throwable类,泛型变量可以
catch子句中不能使用类型变量
可以取消对检查型异常的检查
利用泛型将检查型异常转换为非检查型异常
注意擦除后的冲突
考虑一个Pair< String >,它有两个equals方法,一个是Pair< T >中定义的equals(String),一个是从Object类中继承的equals(Object),然而equals(String)擦除为equals(Object)后,便会产生冲突,而不是覆盖
重新命名
若两个接口是同一泛型接口的不同参数化,若是存在继承关系,则擦除后方法合成的桥方法会与超类的桥方法产生冲突,而不是覆盖
class Employee implements Comparable<Employee>{
public int compareTo(Object other){return compareTo((Employee) other);}
}
class Manager extends Employee implements Comparable<Manager>{
public int compareTo(Object other){return compareTo((Manager) other);}
}
通配符类型
通配符概念
使用?通配符,允许类型参数发生变化
可表示类型变量符合限定的所有的泛型
通配符的类型限定
使用extends关键字指定一个子类型限定,而使用super关键字指定一个超类型限定
继承关系<- | <- | <- | <- | <- |
---|---|---|---|---|
super限定 | Pair | Pair<?> | Pair<? super Manager> | Pair< Employee > Pair< Object > |
extends限定 | Pair | Pair<?> | Pair<? extends Employee> | Pair< Employee > Pair< Manager > |
方法的返回类型可以是返回值的超类型,方法参数的类型可以是接受的参数的子类型
public Pair<? extends Employee> getFirst(){...}
extends限定的通配符类型可以作为返回类型,但不能用于方法参数
因为编译器知道通配符的超类型的具体类型,但无法得知通配符的子类型的具体类型
public void setFirst(Pair<? super Employee> p){...}
super限定的通配符类型不能用于返回类型,但可以用于方法参数
因为编译器无法得知通配符的超类型的具体类型,但知道通配符的子类型的具体类型
即带有超类型限定的通配符允许写入一个泛型对象,子类型限定的通配符允许读取一个泛型对象
无限定通配符
public Pair<?> getFirst(){...}
public void setFirst(Pair<?> p){...}
无限定通配符作为返回类型,返回值只能赋给一个Object
无限定通配符用于方法参数,将不能被调用,只能用于静态方法
public static boolean hasNull(Pair<?> p){...}
public static <T> boolean hasNull(Pair<T> p){...}
通配符捕获
通配符不是类型变量,因此不能将?作为一种类型
可以使用类型参数捕获通配符,虽然不知道通配符指示的是什么类型,但确实是个明确的类型
public static <T> void swapHelper(Pair<T> p) {...}
public static void swapHelper(Pair<?> p) {swapHelper(p);} //捕获
只有在能够保证通配符表示单个确定的类型时,通配符捕获才是合法的。如ArrayList<Pair<?>>保存多个Pair< ? >元素,但?不同
反射与泛型
泛型Class类
String.Class实际上是一个Class< String >类的唯一对象
使用Class< T >参数进行类型匹配
public static <T> Pair<T> makePair(Class<T> c){...}
则调用
Pair.makePair(Employee.Class)
//Pair.<Employee>makePair(Employee.Class)
Employee类将自动与类型参数T匹配
虚拟机中的泛型类型信息
对于java.lang.reflect包中的接口Type,有以下子类型
- Class类,描述类型
- TypeVariable接口,描述类型变量
- WildcardType接口,描述通配符
- ParameterizedType接口,描述泛型类或接口类型
- GeneicArrayType接口,描述泛型数组
类型字面量
无法从一个对象得到泛型类型
字段和方法参数的泛型类型还留存在虚拟机中
CDI和Guice等注入框架使用类型字面量来控制泛型类型的注入