参考:泛型

1 什么是 Java 泛型以及 Java 泛型通配符

1.1 泛型

Java 泛型 generics,可以理解为一种定义的模板,提供编译期类型检测机制,编写模板代码来适应任意类型。
定义泛型方法的规则:

  • 所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前(在下面例子中的)。
  • 每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。
  • 类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符
  • 泛型方法体的声明和其他方法一样。注意类型参数只能代表引用型类型,不能是原始类型(像int,double,char的等)。

所以,

  1. <T>不能是基本类型,例如int,因为实际类型是ObjectObject类型无法持有基本类型。
  2. 无法获取带有泛型的 Class 类型(只能拿到外层的class,与内部泛型无关,即 List 对象的 getClass与 List 的 getClass 相同)。换句话说,所有泛型实例,无论T的类型是什么,getClass()返回同一个Class实例。
  3. 通过 getClass 无法判断泛型类型。
  4. 无法通过泛型 T 实例化对象。(new T() 是不可以使用的)。要实例化T类型,必须借助额外的Class<T>参数:

    1. public Class Demo<T> {
    2. private T t;
    3. public Demo(Class<T> clazz) {
    4. this.t = clazz.newInstance();
    5. }
    6. }

  5. 1.2 泛型使用

  6. 使用泛型,需要将泛型参数 替换为使用的class类型,比如 List, Set;

  7. 自动推断,List list = new LinkedList<>();
  8. 不指定泛型类型,编译器自己自动将 视为 ;
  9. 可以在接口,抽象类等定义泛型,实现类需要明确给定泛型,比如 Comparable 比较接口的实现
  10. 注意:

    1. 泛型的继承关系:可以把ArrayList向上转型为List(T不能变!),但不能把ArrayList向上转型为ArrayList(T不能变成父类)。
    2. 编写泛型类时,要特别注意,泛型类型**<T>**不能用于静态方法,可以有静态泛型方法。即静态方法不能引用泛型类型,必须定义其他类型(例如)来实现静态泛型方法。

      1.3 泛型通配符

      通配符的存在,解决有界泛型使用的场景。

    3. 无限定通配符 ?,类型通配符一般是使用 ? 代替具体的类型参数。无界,不可写入,不可读取。无限定通配符<?>很少使用,可以用替换,同时它是所有类型的超类。

    4. 上界通配符(Upper Bounds Wildcards),一个有界的类型参数,首先列出类型参数的名称,后跟extends关键字,最后紧跟它的上界。<? extends ClassName> 通配符能够读取,不能写入。

      1. public class MaximumTest
      2. {
      3. // 比较三个值并返回最大值
      4. public static <T extends Comparable<T>> T maximum(T x, T y, T z)
      5. {
      6. T max = x; // 假设x是初始最大值
      7. if ( y.compareTo( max ) > 0 ){
      8. max = y; //y 更大
      9. }
      10. if ( z.compareTo( max ) > 0 ){
      11. max = z; // 现在 z 更大
      12. }
      13. return max; // 返回最大对象
      14. }
      15. }
    5. 下界通配符, super,<? super ClassName>通配符作为方法参数,表示方法内部代码对于参数只能写入,不能读取。

    6. PECS原则

    何时使用extends,何时使用super?为了便于记忆,我们可以用PECS原则:Producer Extends Consumer Super。
    即:如果需要返回T,它是生产者(Producer),要使用extends通配符;如果需要写入T,它是消费者(Consumer),要使用super通配符。

    小结

    ? 与 T 泛型的区别,关系:
    ? 是通配符,表示所有 T 的超类。
    ? 类型的不可读,不可写(忘对应的容器中添加泛型对象,读取泛型对象,当然是指不能读取到非 Object 以为的类型,即可以使用 Object obj = get);T 可以。

    作为方法参数,<? extends T>类型和<? super T>类型的区别在于:
    <? extends T>允许调用读方法T get()获取T的引用,但不允许调用写方法set(T)传入T的引用(传入null除外);
    <? super T>允许调用写方法set(T)传入T的引用,但不允许调用读方法T get()获取T的引用(获取Object除外)。
    一个是允许读不允许写,另一个是允许写不允许读。

    1. package com.demo.activiti7.inherit;
    2. import com.alibaba.fastjson.JSON;
    3. import java.util.ArrayList;
    4. import java.util.List;
    5. import org.junit.Test;
    6. public class DemoTest {
    7. class AA<T extends Comparable> {
    8. T first;
    9. T last;
    10. List<T> list = new ArrayList<>();
    11. List<? extends Integer> listExtends = new ArrayList<>();
    12. List<? super Integer> listSupers = new ArrayList<>();
    13. }
    14. public static void main(String[] args) {
    15. DemoTest demoTest = new DemoTest();
    16. AA<Integer> integerAA = demoTest.new AA<>();
    17. integerAA.first = 100;
    18. integerAA.last = 200;
    19. System.out.println(integerAA.first);
    20. System.out.println(integerAA.last);
    21. System.out.println(JSON.toJSONString(integerAA));
    22. integerAA.list.add(integerAA.first);
    23. integerAA.list.add(integerAA.last);
    24. System.out.println(integerAA.list);
    25. // 允许添加 null
    26. integerAA.listExtends.add(null);
    27. // 该行报错 add(Capture<? extends java.lang.Integer>) in List catnot be applied to (java.lang.Integer)
    28. // integerAA.listExtends.add(integerAA.last);
    29. Integer integer = integerAA.listExtends.get(0);
    30. System.out.println("integerAA.listExtends.get(0):" + integerAA.listExtends.get(0));
    31. System.out.println("listExtends:" + integerAA.listExtends);
    32. integerAA.listSupers.add(integerAA.first);
    33. integerAA.listSupers.add(integerAA.last);
    34. // 可以使用 Object 接收对象
    35. Object object = integerAA.listSupers.get(0);
    36. System.out.println("integerAA.listSupers.get(0):" + integerAA.listSupers.get(0));
    37. System.out.println("listSupers:" + integerAA.listSupers);
    38. List<?> lists = new ArrayList<>();
    39. // 报错
    40. // lists.add("a");
    41. Object o = lists.get(0);
    42. }
    43. }

    结果:

    1. Connected to the target VM, address: '127.0.0.1:59632', transport: 'socket'
    2. 100
    3. 200
    4. {}
    5. [100, 200]
    6. integerAA.listExtends.get(0):null
    7. listExtends:[null]
    8. integerAA.listSupers.get(0):100
    9. listSupers:[100, 200]
    10. Disconnected from the target VM, address: '127.0.0.1:59632', transport: 'socket'
    11. Process finished with exit code 0

    所以,java.util.Collections#copy 方法,src 参数是 extends(读取),dest 参数方法是 super(写入)。
    public static void copy(List<? super T> dest, List<? extends T> src)

    注意,所说的读取写入,只是对要操作的对象而言。