类型参数的好处
- 程序更已读,可以明显知道代表的什么类型数据
- 更安全,在存储可以根据类型检查,读取的时候不用强制类型转换
泛型类
public class Pair<T>
{
private T first;
private T second;
}
- 参数类型可以是一个或者多个,用“<>”括起来跟在类名后面
- 一般使用E表示集合的元素类型,K,V表示键和值的类型,T、U表示任意类型
泛型方法
class ArrayAlg
{
public static <T> T getMiddle(T...a)
{
return a[a.length/2];
}
}
- 类型变量放在修饰符的后面,返回类型的前面。
- 可以在泛型类或者普通类中定义。
- 调用泛型方法时,可以将具体类型放在尖括号中,放在方法名前面
- 大多数时候,编译器会根据参数来推断泛型类型,会根据多个参数的的父类往上推,找到共同的父类,如果推导出共同父类不唯一,则会报错
类型变量T的限定
//<T extends BoundingType>
public static <T extends Comparable> T min(T...a)
- T 和 限定类型BoundingType 可以是类,也可以是接口。限定类型是接口时代表实现了该接口,是类时代表继承了该类。
- 一个类型变量或通配符可以有多个限定,如: T extends Comparable & Serialzable 用 “&”分隔多个限定
- 多个限定由最多一个类和多个接口,如果有类时必须放在第一个。 如: T extends Number & Comparable
泛型代码和虚拟机
虚拟机没有泛型类型对象-所有对象都属于普通类,编译过后,类型变量都会被擦除。
类型擦除
- 无论何时定义一个泛型类型,都会自动提供一个相应的原始类型。这个原始类型的名字就是去掉类型参数后的泛型类型名
// Pair<T>的原始类型如下:
public class Pair(){
private Object first;
//........
}
//Pair<String> Pair<Integer> 擦除类型后都是 Pair类型
- 类型变量会被擦除,并替换为其第一个限定类型(无限定类型则替换成Object)
- 多个限定类型时,应该将没有方法的标签接口放在最后面
转换泛型表达式
- 访问泛型方法或者泛型字段时,类型擦除后,返回类型为第一限定类型或者Object,编译器会插入强制类型转换来返回具体类型
转换泛型方法
泛型转换总结
- 虚拟机中没有泛型,只有普通的类和方法
- 所有的类型参数T都会转换为他们的限定类型
- 会合成桥方法来保持多态
- 为保持类型安全性,必要时会插入强制类型转换
限制与局限性
-
不能用基本类型实例化类型参数
类型擦除后,类型参数为限定类型,不能接口基本类型,可以使用对应的包装类型 -
运行时类型查询只适用于原始类型
虚拟机中的对象都有一个特定的非泛型类型,也就是Class对象。所有泛型类型对象查询只返回擦除后的原始类型
比如 PairPair 的getClass方法返回的都是Pair.class -
不能创建参数化类型的数组,但可以声明,可以使用ArrayList来收集参数化类型队形,更安全,有效
Pair<String>[] data ;//true
data = new Pair<String>[10] //Error
ArrayList<Pair<String>> list = new ArrayList<>();//安全、有效
- Varagrs可变参数警告
public static <T> void addAll(Collection<T> coll,T...ts)
{
for(T t:ts) coll.add(t);
}
- 向可变参数传递泛型类型的实例,虚拟机就会创建一个参数化类型数组,违背了上一条规则,此时规则会放松,会产生一个警告,
可以通过@SuppressWarning("Unchecked") 或者 @SafeVaragrs 注解来抑制警告 - @SafeVaragrs注解只能用于 final、static或者(Java9中)private的构造器和方法,也就是不允许被覆盖的方法。
- 可以使用@SafeVarargs来创建泛型数组
@SafeVarargs static <E> E[] array(E...array) {return array;}
Pair<String> table = array(pair1,pair2);
//这看起来可以创建泛型数组了,不过很危险。
Object[] objarray = table;
objarray[0] = new Pair<Integer>();//可以顺利存储,但是处理table【0】时就会出现异常
- 不能实例化类型变量T
//构造方法
public Pair() { first = new T(); second = new T();}//Error,因为T类型会被擦除
//可以通过传递参数类型来实例化
public static <T> Pair<T> makePair<Class<T> cl>
{
return new Pair<>(cl.getConstructor().newInstance(),cl.getConstructor().newInstance())
}
- 不能构造泛型(类型变量T)数组
public static <T> T[] makeArray(T...a)
{
T[] = new T[2];//Error 不能构造类型变量T数组,会被擦除
}
//通过接收数组构造器表达式来创建泛型数组
public static <T> T[] makeArray(IntFunction<T[]> constr,T...a)
{
T[] result = constr.apply(2);
...
}
String[] names = makeArray(String[]::new,"aa","bb");
- 泛型类的静态上下文中类型变量无效
public class Pair<T>{
private static T first;// Error
//Error
public static T getT(){
return first;
}
}
-
不能抛出或捕获泛型类的实例
泛型类不能继承自Throwable,也不能再catch块中捕获类型变量T -
可以取消对检查型异常的检查
Java异常处理的基本准则是必须为所有检查型异常提供一个处理器。可以利用以下方法取消这个机制
@SuppressWarning("unChecked")
static <T extends Throwable> void throwAs(Throwable t) throws T
{
throw (T) t;
}
- 注意擦除后的冲突
比如泛型类中定义一个equals方法,类型擦除后与Object中继承的方法冲突
如果两个接口类型是同一接口的不同参数化,一个类或类型变量就不能同时作为这两个接口类型的子
泛型类型的继承规则
- Pair
与 Pair 没有关系 - ArrayList
与 List 是继承关系