什么是泛型?

泛型是一种“代码模板”,可以用一套代码套用各自类型。一般约定泛型使用 T 声明,T 可以是任何 class

向上转型

Java 标准库中 ArraryList<T> 实现了 List<T> 接口,它可以向上转型为 List<T>,即类型 ArrayList<T> 可以向上转型为 List<T>,但是 T 不能变。

注意,ArraryList<Integer> 不能向上转型为 ArraryList<Number> ,它们两者没有任何继承关系

  1. public class ArrayList<T> implements List<T> {
  2. ...
  3. }
  4. List<String> list = new ArrayList<String>();

类型推断

编译器如果能自动推断出泛型类型,就可以省略后面的泛型类型

  1. List<String> list = new ArrayList<>();

使用 var 声明变量时,编译器会将泛型类型自动推断为 Object 类型

泛型类

定义泛型类

Java 标准库中集合框架中的接口和类均是泛型接口和类,使用这些接口和类是,把泛型参数替换为需要的 class 类型,例如 ArrayList<String>

  1. // E 为泛型形参
  2. public interface List<E> extends Collection<E> {
  3. }

实现泛型接口的泛型类必须实现正确的泛型类型。如 ArrayList<E> 泛型类实现 List<E> 泛型接口

  1. public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
  2. }
  3. // 使用时指定具体类型
  4. List<String> strList = new ArrayList<>();

泛型新参可以定义多种类型,需要定义多个类型时,只要区分不同的新参名即可

  1. public class HashMap<K,V> extends AbstractMap<K,V>
  2. implements Map<K,V>, Cloneable, Serializable {
  3. }
  4. // 使用时分别指定具体类型
  5. HashMap<String,String> map = new HashMap<>();

并不存在真正的泛型类

声明 ArrayList<String> 类时,系统并没有为 ArrayList<String> 生成新的 class 文件,并且也不会将它当成新的类来处理,ArrayList<String> 类的 Class 仍是 ArrayList.ClassArrayList<Number> 等其他类型同理都是 ArrayList.Class

  1. List<String> strings = new ArrayList<>(16);
  2. List<Integer> integers = new ArrayList<>(16);
  3. final var stringsClass = strings.getClass();
  4. log.debug(stringsClass.toString()); // class java.util.ArrayList
  5. final var integersClass = integers.getClass();
  6. log.debug(integersClass.toString()); // class java.util.ArrayList
  7. log.debug(String.valueOf(stringsClass == integersClass)); // true

类型通配符(泛型约束)

Java 使用问号 ? 作为无限定通配符,它对应的元素类型可以匹配任意类型。如 List<?> 可以匹配任意 class 类型的 List

extend 关键字设定通配符的上限

使用 extend 关键字限定泛型类中 T 类型必须是指定类的子类

  1. // 限定 T 类型必须是 Number 类型的子类
  2. public class Pair<T extends Number> {
  3. }
  4. // T 类型本身就是 Number 类型
  5. Pair<Number> numberPair = new Pair<>(1, 22.2);
  6. // Integer 和 Double 都是 Number 类型的子类
  7. Pair<Integer> integerPair = new Pair<>(22, 21);
  8. Pair<Double> doublePair = new Pair<>(22.1, 212.2);
  9. // T 类型非 Number 类型时编译失败
  10. Pair<String> stringPair = new Pair<String>("", "");

指定通配符的上限就是让类支持类型型变。比如 IntegerNumber 的子类,那么 Pair<Integer> 就相当于 Pair<? extend Number> 的子类,可以将 Pair<Integer> 赋值给 Pair<? extend Number> 类型的变量,这种型变方式称为协变。 对于协变的泛型类来说,它只能调用泛型类型作为返回值类型的方法;而不能调用泛型类型作为参数的方法,就是说:协变只出不进

泛型方法

定义泛型方法

  1. static int add(Pair<?> pair){
  2. Number first = pair.getFirst();
  3. Number last = pair.getLast();
  4. return first.intValue() + last.intValue();
  5. }

类型通配符(泛型约束)

extend 关键字设定通配符的上限

使用 extend 关键字限定泛型方法参数时,方法内部只能调用获取指定类的引用的方法,不能调用传入指定类的引用的方法。即只能读,不能写

  1. @Data
  2. @AllArgsConstructor
  3. public class Pair<T> {
  4. private T first;
  5. private T last;
  6. }
  7. static int add(Pair<? extends Number> pair){
  8. Number first = pair.getFirst();
  9. Number last = pair.getLast();
  10. // 编译失败
  11. pair.setFirst(1);
  12. return first.intValue() + last.intValue();
  13. }

super 关键字设置通配符的下限

和 extend 通配符相反,方法内部只能调用传入指定类的引用的方法。即只能写,不能读

  1. @Data
  2. @AllArgsConstructor
  3. public class Pair<T> {
  4. private T first;
  5. private T last;
  6. }
  7. static int add(Pair<? super Integer> pair){
  8. // 编译失败
  9. // 因为如果传入的是 Number 类型,编译器无法将 Number 类型转为 Integer 类型
  10. Integer first = pair.getFirst();
  11. // 因为如果传入的是 Number 类型是抽象类,无法直接实例化它
  12. Number last = pair.getLast();
  13. // 只能使用 Object 类型接收返回值
  14. Object first = pair.getFirst();
  15. return first.intValue() + last.intValue();
  16. }

擦拭法

Java 语言的泛型实现方法是擦拭法:即虚拟机 JVM 本身对泛型一无所知,所有的工作都是编译器做的。编译器内部永远把类型 T 视为 Object 处理,但是在需要类型转换的时候,编译器会根据 T 的类型自动实现安全的类型强制转换
定义泛型类 Pair<T>,在编译器中的代码:

  1. public class Pair<T> {
  2. private T first;
  3. private T last;
  4. public Pair(T first, T last) {
  5. this.first = first;
  6. this.last = last;
  7. }
  8. public T getFirst() {
  9. return first;
  10. }
  11. public T getLast() {
  12. return last;
  13. }
  14. }

虚拟机 JVM 实际执行的代码:

  1. public class Pair {
  2. private Object first;
  3. private Object last;
  4. public Pair(Object first, Object last) {
  5. this.first = first;
  6. this.last = last;
  7. }
  8. public Object getFirst() {
  9. return first;
  10. }
  11. public Object getLast() {
  12. return last;
  13. }
  14. }

在编译器中使用泛型的代码:

  1. Pair<String> p = new Pair<>("Hello", "world");
  2. String first = p.getFirst();
  3. String last = p.getLast();

虚拟机 JVM 实际执行的代码并没有泛型:

  1. Pair p = new Pair("Hello", "world");
  2. String first = (String) p.getFirst();
  3. String last = (String) p.getLast();

Java 泛型局限

  1. 局限一:泛型 T 不能是基本类型,例如 int。因为实际类型都是 ObjectObject 类型无法持有基本类型
  2. 局限二:无法取得带泛型的 Class。所有泛型实例,无论 T 的类型是什么,getClass() 返回的都是同一个 Class 实例,因为它们在编译后都是 Pair<Object>
  3. 局限三:无法判断带泛型的类型
  4. 局限四:不能实例化 T 类型