什么是泛型?
泛型是一种“代码模板”,可以用一套代码套用各自类型。一般约定泛型使用 T 声明,T 可以是任何 class
向上转型
Java 标准库中 ArraryList<T> 实现了 List<T> 接口,它可以向上转型为 List<T>,即类型 ArrayList<T> 可以向上转型为 List<T>,但是 T 不能变。
注意,
ArraryList<Integer>不能向上转型为ArraryList<Number>,它们两者没有任何继承关系
public class ArrayList<T> implements List<T> {...}List<String> list = new ArrayList<String>();
类型推断
编译器如果能自动推断出泛型类型,就可以省略后面的泛型类型
List<String> list = new ArrayList<>();
使用
var声明变量时,编译器会将泛型类型自动推断为Object类型
泛型类
定义泛型类
Java 标准库中集合框架中的接口和类均是泛型接口和类,使用这些接口和类是,把泛型参数替换为需要的 class 类型,例如 ArrayList<String>
// E 为泛型形参public interface List<E> extends Collection<E> {}
实现泛型接口的泛型类必须实现正确的泛型类型。如 ArrayList<E> 泛型类实现 List<E> 泛型接口
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {}// 使用时指定具体类型List<String> strList = new ArrayList<>();
泛型新参可以定义多种类型,需要定义多个类型时,只要区分不同的新参名即可
public class HashMap<K,V> extends AbstractMap<K,V>implements Map<K,V>, Cloneable, Serializable {}// 使用时分别指定具体类型HashMap<String,String> map = new HashMap<>();
并不存在真正的泛型类
声明 ArrayList<String> 类时,系统并没有为 ArrayList<String> 生成新的 class 文件,并且也不会将它当成新的类来处理,ArrayList<String> 类的 Class 仍是 ArrayList.Class,ArrayList<Number> 等其他类型同理都是 ArrayList.Class
List<String> strings = new ArrayList<>(16);List<Integer> integers = new ArrayList<>(16);final var stringsClass = strings.getClass();log.debug(stringsClass.toString()); // class java.util.ArrayListfinal var integersClass = integers.getClass();log.debug(integersClass.toString()); // class java.util.ArrayListlog.debug(String.valueOf(stringsClass == integersClass)); // true
类型通配符(泛型约束)
Java 使用问号 ? 作为无限定通配符,它对应的元素类型可以匹配任意类型。如 List<?> 可以匹配任意 class 类型的 List
extend 关键字设定通配符的上限
使用 extend 关键字限定泛型类中 T 类型必须是指定类的子类
// 限定 T 类型必须是 Number 类型的子类public class Pair<T extends Number> {}// T 类型本身就是 Number 类型Pair<Number> numberPair = new Pair<>(1, 22.2);// Integer 和 Double 都是 Number 类型的子类Pair<Integer> integerPair = new Pair<>(22, 21);Pair<Double> doublePair = new Pair<>(22.1, 212.2);// T 类型非 Number 类型时编译失败Pair<String> stringPair = new Pair<String>("", "");
指定通配符的上限就是让类支持类型型变。比如
Integer是Number的子类,那么Pair<Integer>就相当于Pair<? extend Number>的子类,可以将Pair<Integer>赋值给Pair<? extend Number>类型的变量,这种型变方式称为协变。 对于协变的泛型类来说,它只能调用泛型类型作为返回值类型的方法;而不能调用泛型类型作为参数的方法,就是说:协变只出不进
泛型方法
定义泛型方法
static int add(Pair<?> pair){Number first = pair.getFirst();Number last = pair.getLast();return first.intValue() + last.intValue();}
类型通配符(泛型约束)
extend 关键字设定通配符的上限
使用 extend 关键字限定泛型方法参数时,方法内部只能调用获取指定类的引用的方法,不能调用传入指定类的引用的方法。即只能读,不能写
@Data@AllArgsConstructorpublic class Pair<T> {private T first;private T last;}static int add(Pair<? extends Number> pair){Number first = pair.getFirst();Number last = pair.getLast();// 编译失败pair.setFirst(1);return first.intValue() + last.intValue();}
super 关键字设置通配符的下限
和 extend 通配符相反,方法内部只能调用传入指定类的引用的方法。即只能写,不能读
@Data@AllArgsConstructorpublic class Pair<T> {private T first;private T last;}static int add(Pair<? super Integer> pair){// 编译失败// 因为如果传入的是 Number 类型,编译器无法将 Number 类型转为 Integer 类型Integer first = pair.getFirst();// 因为如果传入的是 Number 类型是抽象类,无法直接实例化它Number last = pair.getLast();// 只能使用 Object 类型接收返回值Object first = pair.getFirst();return first.intValue() + last.intValue();}
擦拭法
Java 语言的泛型实现方法是擦拭法:即虚拟机 JVM 本身对泛型一无所知,所有的工作都是编译器做的。编译器内部永远把类型 T 视为 Object 处理,但是在需要类型转换的时候,编译器会根据 T 的类型自动实现安全的类型强制转换
定义泛型类 Pair<T>,在编译器中的代码:
public class Pair<T> {private T first;private T last;public Pair(T first, T last) {this.first = first;this.last = last;}public T getFirst() {return first;}public T getLast() {return last;}}
虚拟机 JVM 实际执行的代码:
public class Pair {private Object first;private Object last;public Pair(Object first, Object last) {this.first = first;this.last = last;}public Object getFirst() {return first;}public Object getLast() {return last;}}
在编译器中使用泛型的代码:
Pair<String> p = new Pair<>("Hello", "world");String first = p.getFirst();String last = p.getLast();
虚拟机 JVM 实际执行的代码并没有泛型:
Pair p = new Pair("Hello", "world");String first = (String) p.getFirst();String last = (String) p.getLast();
Java 泛型局限
- 局限一:泛型
T不能是基本类型,例如int。因为实际类型都是Object,Object类型无法持有基本类型 - 局限二:无法取得带泛型的
Class。所有泛型实例,无论T的类型是什么,getClass()返回的都是同一个Class实例,因为它们在编译后都是Pair<Object> - 局限三:无法判断带泛型的类型
- 局限四:不能实例化
T类型
