要点:
- 泛型类是带有一个或多个类型参数的类
- 泛型方法是带有类型参数的方法
- 可以要求类型参数必须是一个或者多个类型的子类型
- 泛型类型是不变得:当S是T的子类型是,G<S>和G<T>没有关系
- 通过使用通配符G<? extends T>或者 G<? super T>,你可以指定一个方法接受一个带子类或父类参数的泛型类型的实例
- 当泛型类和方法被编译时,类型参数会被擦除
- 类型擦除对泛型有诸多限制。特别是,不能实例化泛型类或数组。不能转换成泛型类型,或者抛出一个泛型类型对象。
- 即使泛型类和方法在虚拟机中被擦除,你也可以在运行时找出他们是如何声明的。
1、泛型类
※类型参数不能用基本类型实例化。例如Entry<String, int>在Java中是无效的。
当构造一个泛型类对象时,可以在构造函数中省略类型参数。
1 Entry<String, Integer> entry = new Entry<>("1",1);
2、泛型方法
当声明一个反洗方法时,类型参数要放在修饰符之后,返回类型之前。当调用时,不要指定类型参数。它可以从方法的参数和返回类型中推断出来。
也可以在调用时候显示的写出来
1 public static <T> void swap(T[] array, int i, int j);
2 Arrays.<String>swap(friends,0,1);
3、类型限定
泛型类或方法的类型参数需要满足某些要求,或者是需要能够调用一些通用的方法。可以对类型做出限定。
T extends AutoCloseable
类型参数可以有多个限定: T extends Runnable & AutoCloseable
※可以有多个接口限定,但最多只能有一个是类。如果有一个限定是类,则它必须放在限定列表的第一位。
子类通配符:? extends Employee 代表任意一个Employee的子类型
父类通配符:? super Employee 代表Employee的一个父类型 (经常用作函数式对象的参数)
一些程序员喜欢用通配的"PECS"助记码:生产者用extends,消费者用super,从List读用extends,赋值用super
4、通配符捕获
1 public static void swap(ArrayList<?> elements, int i,int j){
2 ? temp = elements.get(i); //不可行
3 ...
4 }
可以将?当做一个类型参数,但不能用作一个类型。但是可以用通配符捕获规则绕过他
public static void swap(ArrayList<?> elements, int i,int j){
swapHelper(elements, i, j);
}
private static<T> void swapHelper(ArrayList<T> elements, int i,int j){
T temp = elements.get(i);
elements.set(i, elements.get(j));
elements.set(j, temp);
}
编译器不知道?是什么,但知道它代表着某种类型,所以能调用一个泛型方法。方法的类型参数T捕获了通配符类型,这样用户就看见了一个容易理解的ArrayList<?>,而不是一个泛型方法。
5、泛型擦除
在java虚拟机中泛型类型会被擦除,它会被编译成一个原始类型。一般的T编译成Object。
如果泛型有限定,他就会被第一个限定所替代。
public class Entry<K extends Comparable<? super K> & Serializable>,V extends Serializable>
//擦除后
public class Entry{
private Comparable key;
private Serializable value;
}
6、桥方法
当使用泛型时,如果定义了与基类相同的方法,编译器会自动添加一个桥方法来调用自定义的方法P216,桥方法不仅用于泛型类型,也用在实现协变的返回类型。
7、泛型的约束
1、无基本类型参数,泛型必须使用对象类型,这样为了处理Integer会出现大量的函数式接口
2、所有类型在运行时都是原始的,需要做类型转换。
getClass方法总是返回一个原始类型。ArrayList<String>,list.getClass()返回的是ArrayList.class。不存在ArrayList<String>.class
在类名称中也不能有类型变量。没有T.class、T[].class或者ArrayList<T>.class
3、不能实例化类型变量
不能new T()或者new T[..]
public static <T>T[] repeat(in n,T obj){
T[] result = new T[n];//错误,不能用new[...]构造一个数组
}
为了解决这个问题,让调用者以引用的方式提供数组的构造函数
String[] greetings = Arrays.repeat(10,"Hi",String[]::new);
4、不能构造参数化类型的数组
5、静态上下文中的类类型变量不是有效的,不能再静态变量或者静态方法中使用类型变量K和V
6、类型擦除后的方法可能冲突
public interface Ordered<T> extends Comparable<T>{
public default boolean equals(T value){
//错误,擦除后与Object.equals冲突
...
}
}
public class Employee implements Comparable<Employee>{
}
public class Manager extends Employee implements Comparable<Manager>{}
//这里有几个方法
public int comparaTo(Employee other)
public int compareTo(Manager other)
//但是,桥方法冲突
public int comparaTo(Object other)
8、虚拟机中的泛型类型信息
使用TypeVariable<D>接口时泛型的,类型参数是Class<T>、Method或者Constructor<T>
//获取ArrayList类的类型变量
TypeVariable<Class<ArrayList>>[] vars = ArrayList.class.getTypeParameters();
String name = vars[0].getName();
//获得Method总的泛型类型
Method m = Collections.class.getMethod("", List.class);
TypeVariable<Method>[] vars = m.getTypeParameters();
String name = vars[0].getName();
关键点是泛型类和泛型方法的声明没有被擦除,并且你可以通过反射访问他们。