1,泛型的引入
先看一个关于ArrayList的使用案例
ArrayList collection = new ArrayList();
collection.add(123);
collection.add("456");
collection.add(12.3);
for(Object element : collection) {
System.out.println((String) element);
// java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
}
Java的泛型是在JDK5引入的新特性,它可以让编写的代码应用于不同类型的对象(ArrayList<String>、ArrayList<Integer>
),大大的增加了代码的复用性;同时通过类型限定提供了编译时类型安全检测机制,将代码的检测提前到了编译阶段,避免运行时错误,增加了代码的健壮性。
2,泛型的分类
- 泛型接口,格式是:
class IntefaceNme<T1, T2, ..., Tn> [extends ...]
public interface List<E> extends Collection<E>
- 泛型类,格式是:
class ClazzNme<T1, T2, ..., Tn> [extends ...] [implements ...]
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
- 泛型方法,格式是:
[public] [static] <T> 返回值类型 方法名(T 参数列表)
public static <T> void sort(List<T> list, Comparator<? super T> c) {
list.sort(c);
}
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数,常用的类型参数有:
- K: 键
- V: 值
- N: 数字
- T: 类型
- E: 元素
- S, U, V, R等,泛型声明的多个类型
3,泛型的限定、继承
1,某些特定场合下,需要对的泛型类所操作的数据类型进行限定,要求它必须继承某个类或实现某些接口,如下面这个泛型方法就要求引入的类型必须实现Comparable接口:
public static <T extends Comparable> T getMin(T... array) {
if (null == array || array.length <= 0) {
return null;
}
T min = array[0];
for(int i = 1; i < array.length; i++) {
if (min.compareTo(array[i]) > 0) {
min = array[i];
}
}
return min;
}
泛型限定语法:extends限定可以有多个接口但只能有一个类,且类必须排第一位,用”&“符号连接,多个引入类型使用“,”分割,如:<T extends File & Cloneable, U extends Serializable>要求T必须同时实现File和Cloneable接口,U必须实现Serializable接口
2,Basket<T>与Basket<S>没有任何关系,无论S和T有着怎样的关系
Basket<Apple> appleBasket = new Basket<>();
Basket<Fruit> fruitBasket = appleBasket;
// compile error: Incompatible types, require Basket<Fruit> found Basket<Apple>
3,泛型类或者接口的继承与实现要么是继续声明泛型类型,要么指明实际类型参数
public interface Collection<E> extends Iterable<E>
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
class MyStringList extends ArrayList<String>
4,通配符类型与PECS原则
1,通配符"?"代表类型未知,对于List<?>中的元素只能用Object来引用且无法添加元素,它的使用场景一般是泛型类中的方法不依赖于类型参数的时候,比如list.size(), 遍历集合等
2,PECS(Producer Extends, Consumer Super)原则
- 要从泛型类读取类型T的数据,并且不能写入,可以使用
?extends T
上限限定符,此时泛型类可以视作生产者,它能接收的类型是T或T的子类,从中获取的数据都可向上转型为T - 要向泛型类写入类型T的数据,并且不需要读取(读取的数据也只能由Object类型引用),可以使用
?super T
下限界定符,此时泛型类型可视作消费者,它能接收的类型是T或T的超类,只要是能向上转型为T的数据都能往里塞 - 如果既想写入又想读出,那就不要用通配符
class Fruit {}
class Apple extends Fruit {}
class GreenApple extends Apple {}
class Orange extends Fruit {}
class StringList extends ArrayList<String> {}
public class Basket<T> {
private List<T> basketHolder = new ArrayList<>();
public void set(T item) {
basketHolder.add(item);
}
public T get() {
if (!basketHolder.isEmpty()) {
return basketHolder.get(0);
}
return null;
}
public static void main(String[] args) {
Basket<? extends Apple> appleBasket1 = new Basket<Apple>();
/* ? extends上限界定符不可写入
appleBasket1.set(new Fruit());
appleBasket1.set(new Apple());
appleBasket1.set(new GreenApple());
appleBasket1.set(new Orange());*/
Apple apple = appleBasket1.get();
Basket<? super Apple> appleBasket2 = new Basket<Apple>();
// 下限界定符只能写入Apple及其子类
//appleBasket2.set(new Fruit());
appleBasket2.set(new Apple());
appleBasket2.set(new GreenApple());
//appleBasket2.set(new Orange());
Object object = appleBasket2.get();
}
}
5,类型擦除
- 虚拟机其实是不支持泛型,在JVM运行时根本就不存在泛型信息,编译器会把泛型类型中所有的类型参数替换为它们的上(下)限,如果没有对类型参数做出限制,那么就替换为Object类型,这样就不需要产生新的类型,所有的泛型类型最终都是一种原始类型,所以Java实现的是一种伪泛型机制。
- 擦除泛型变量后,为保证类型的安全性,编译器会自动完成类型转换。
- 当编译一个扩展泛型类的类,或一个实现了泛型接口的接口时,编译器会通过自动桥方法完成多态方法的调用
6,泛型约束
- 运行时类型查询只适用于原始类型
- 基本类型是不能够作为泛型类型的,需要使用它们对应的包装类。
// error
Map<int,char> wrongMap = new HashMap<>();
// OK
Map<Integer,Character> okMap= new HashMap<>();
- 泛型类型可以理解为一个抽象类型,只是代表了类型的抽象,因此我们不能直接实例化它
public void test(T param){
// error
param = new T();
}
- 无法进行 instanceof 判断(无界通配符 <?>除外)
public static <E> void test(List<E> list) {
// error
if (list instanceof ArrayList<Integer>) {
}
// ok
if (list instanceof ArrayList<?>) {
}
}
-
不能创建参数化类型的数组,主要是为了避免数组里出现类型不一致的元素
-
泛型类的静态上下文中类型变量无效,类( Class )的类型参数( Type Parameters )只存在于类的实例的方法域和成员变量域。换句话说,必须等到实例化泛型类才知道那个类型参数“ T ”是什么鬼。
-
不能直接或者间接扩展 Throwable.因为异常处理是由JVM在运行时刻来进行的,由于类型信息被擦除,JVM是无法区分两个异常类型MyException和MyException的,对于JVM来说,它们都是 MyException类型的,也就无法执行与异常对应的catch语句。
// 不能间接地扩展 Throwable
class IndirectException<T> extends Exception {}
// 不能直接地扩展 Throwable
class DirectException<T> extends Throwable {}
- 类型擦除后引发方法冲突
// 由于泛型擦除的原因,以下方法的将不会视为重载且无法编译
public void set(Set<String> strSet) { }
public void set(Set<Integer> intSet) { }
7,协变与逆变
类型变换( type variance)形式化定义:
- A、B代表两种类型,f(.)表示类型转换,<=代表继承关系,如A<=B表示A继承于B
- f(.)是协变(covariant)的,如果A<=B,有f(A)<=f(B)
- f(.)是逆变(contravariant)的,如果A<=B,有f(B)<=f(A)
- f(.)是不变(invariant)的,上述两种情况都不成立,即f(A)与f(B)没有关系
- f(.)是双变(bivariant)的,如果A<=B,有f(A)<=f(B)和f(B)<=f(A)同时成立
举类: - Java数组是协变的
Fruit[] fruits = new Apple[3];
fruits[0] = new Apple();
// compile ok, runtime error: ArrayStoreException
fruits[1] = new Fruit();
// ok, GreenApple is Apple's subclass
fruits[2] = new GreenApple();
- 不带通配符的泛型是不变的
// compile error
ArrayList<Fruit> fruits = new ArrayList<Apple>();
- 泛型可以采用通配符来支持协变和逆变(PECS)
// 协变
ArrayList<? extends Fruit> fruits1 = new ArrayList<Apple>();
// 逆变
ArrayList<? super GreenApple> fruits2 = new ArrayList<Apple>();