java基础之泛型(整理)

二.Java泛型通配符总结

本文参考了 Java泛型详解 和 Java 之泛型通配符 ? extends T 与 ? super T 解惑

泛型基础
java泛型擦除的概念,感觉大家都熟悉,首先, 说到 Java 的泛型, 我们必须要提到的是Java 泛型的类型擦除机制: Java中的泛型基本上都是在编译器这个层次来实现的. 在生成的 Java 字节代码中是不包含泛型中的类型信息的。所以感觉这个<>符号更多是个表面,底层系统压根不知道这个<>符号存在的意义。使用泛型的时候加上的类型参数, 会被编译器在编译的时候去掉, 这个过程就称为类型擦除。

如在代码中定义的List和List等类型, 在编译之后都会变成List, JVM看到的只是List, 而由泛型附加的类型信息对JVM来说是不可见的。

至于为什么java需要这样实现java 泛型,这样做的目的是因为Java泛型是1.5之后才被引入的,为了保持向下的兼容性,所以只能做类型擦除来兼容以前的非泛型代码。对于这一点,如果阅读Java集合框架的源码,可以发现有些类其实并不支持泛型

<?>是什么
在使用泛型类时, 我们可以使用一个具体的类型, 例如可以定义一个 List 的对象, 我们的泛型参数就是 Integer; 我们也可以使用通配符 ? 来表示一个未知类型, 例如 List<?> 就表示了泛型参数是某个类型, 只不过我们并不知道它的具体类型时什么。

那么如何使用这个List<?>呢?

List<?>所声明的就是所有类型都是可以的, 但需要注意的是, List<?>并不等同于List. 对于 List 来说, 它实际上确定了 List 中包含的是 Object 及其子类, 我们可以使用 Object 类型来接收它的元素. 相对地, List<?> 则表示其中所包含的元素类型是不确定, 其中可能包含的是 String, 也可能是 Integer. 如果它包含了 String 的话, 往里面添加 Integer 类型的元素就是错误的 作为对比, 我们可以给一个 List 添加 String 元素, 也可以添加 Integer 类型的元素, 因为它们都是 Object 的子类

我们可以做的再绝一点

List<?> list = new ArrayList<E>();
list.add(new Object()); //报错,哦吼,List<?>连 Object都不让放!
1
2
正因为类型未知, 我们就不能通过 new ArrayList<?>() 的方法来创建一个新的ArrayList 对象, 因为编译器无法知道具体的类型是什么. 但是对于 List<?> 中的元素, 我们却都可以使用 Object 来接收, 因为虽然类型未知, 但肯定是Object及其子类。这么看来,还可以取出操作,只能执行Object类型元素!!

边界符
现在我们要实现这样一个功能,查找一个泛型数组中大于某个特定元素的个数,我们可以这样实现:

public static <T> int countGreaterThan(T[] anArray, T elem) {
int count = 0;
for (T e : anArray)
if (e > elem) // compiler error
++count;
return count;
}
但是这样很明显是错误的,因为除了short, int, double, long, float, byte, char等原始类型,其他的类并不一定能使用操作符>,所以编译器报错,那怎么解决这个问题呢?答案是使用边界符。

public interface Comparable {undefined
public int compareTo(T o);
}
做一个类似于下面这样的声明,这样就等于告诉编译器类型参数T代表的都是实现了Comparable接口的类,这样等于告诉编译器它们都至少实现了compareTo方法。

public static <T extends Comparable<T>> int countGreaterThan(T[] anArray, T elem) {
int count = 0;
for (T e : anArray)
if (e.compareTo(elem) > 0)
++count;
return count;
}

然后进行擦除就会出现类似下面的代码,也就是说类型T会被擦除为Comparable

public static Comparable int countGreaterThan(Comparable[] anArray, Comparable elem) {
int count = 0;
for (Comparable e : anArray)
if (e.compareTo(elem) > 0)
++count;
return count;
}

PECS原则
? extends T
? extends T 描述了通配符上界, 即具体的泛型参数需要满足条件: 泛型参数必须是 T 类型或它的子类, 例如:

List<? extends Number> numberArray = new ArrayList<Number>(); // Number 是 Number 类型的
List<? extends Number> numberArray = new ArrayList<Integer>(); // Integer 是 Number 的子类
List<? extends Number> numberArray = new ArrayList<Double>(); // Double 是 Number 的子类
1
2
3
上面三个操作都是合法的, 因为 ? extends Number 规定了泛型通配符的上界, 即我们实际上的泛型必须要是 Number 类型或者是它的子类, 而 Number, Integer, Double 显然都是 Number 的子类(类型相同的也可以, 即这里我们可以认为 Number 是 Number 的子类).

关于读取
根据上面的例子, 对于 List<? extends Number> numberArray 对象:

我们能够从 numberArray 中读取到 Number 对象, 因为 numberArray 中包含的元素是 Number 类型或 Number 的子类型.

我们不能从 numberArray 中读取到 Integer 类型, 因为 numberArray 中可能保存的是 Double 类型.
同理, 我们也不能从 numberArray 中读取到 Double 类型.

关于写入
根据上面的例子, 对于 List<? extends Number> numberArray 对象:

我们不能添加 Number 到 numberArray 中, 因为 numberArray 有可能是List<Double> 类型

我们不能添加 Integer 到 numberArray 中, 因为 numberArray 有可能是 List<Double> 类型

我们不能添加 Double 到 numberArray 中, 因为 numberArray 有可能是 List<Integer> 类型

即, 我们不能添加任何对象到 List<? extends T> 中, 因为我们不能确定一个 List<? extends T> 对象实际的类型是什么, 因此就不能确定插入的元素的类型是否和这个 List 匹配. List<? extends T> 唯一能保证的是我们从这个 list 中读取的元素一定是一个 T 类型的

? super T
? super T 描述了通配符下界, 即具体的泛型参数需要满足条件: 泛型参数必须是 T 类型或它的父类, 例如:

// 在这里, Integer 可以认为是 Integer 的 “父类”
List<? super Integer> array = new ArrayList();
// Number 是 Integer 的 父类
List<? super Integer> array = new ArrayList();
// Object 是 Integer 的 父类
List<? super Integer> array = new ArrayList();

关于读取
对于上面的例子中的 List<? super Integer> array 对象:

我们不能保证可以从 array 对象中读取到 Integer 类型的数据, 因为 array 可能是 List<Number> 类型的.

我们不能保证可以从 array 对象中读取到 Number 类型的数据, 因为 array 可能是 List<Object> 类型的.

唯一能够保证的是, 我们可以从 array 中获取到一个 Object 对象的实例

关于写
对于上面的例子中的 List<? super Integer> array 对象:

我们可以添加 Integer 对象到 array 中, 也可以添加 Integer 的子类对象到 array 中.

我们不能添加 Double/Number/Object 等不是 Integer 的子类的对象到 array 中.
1
2
3
容易混淆的地方
List<? super Integer> l1 = ...
List<? extends Integer> l2 = ..
1
2
? super Integer 和 ? extends Integer 限制的其实是 泛型参数, 即 List<? super Integer> l1 表示 l1 的泛型参数 T 必须要满足 T 是 Integer 的父类, 因此诸如 List, List<Number 的对象就可以赋值到 l1 中. 正因为我们知道了 l1 中的泛型参数的边界信息, 因此我们就可以向 l1 中添加 Integer 对象了,

PECE 原则: Producer Extends, Consumer Super

Producer extends: 如果我们需要一个 List 提供类型为 T 的数据(即希望从 List 中读取 T 类型的数据), 那么我们需要使用 ? extends T, 例如 List<? extends Number>. 但是我们不能向这个 List 添加数据.

<? extends Number> 类型一个[Number,?)的集合范围,泛型必须落在这个范围内,如果泛型实为Integer,
那么Double的数据肯定无法插入到 List<Integer>,因为Double也不是Integer子类,无法被看作为Intger对象,所以插入受限!

反之,集合中的数据必然是Number类型或其子类,自然我们可以把集合中数据都当成Number类型去取出数据。

Consumer Super: 如果我们需要一个 List 来消费 T 类型的数据(即希望将 T 类型的数据写入 List 中), 那么我们需要使用 ? super T, 例如 List<? super Integer>. 但是这个 List 不能保证从它读取的数据的类型.

<? super Integer> 类型一个(?,Integer]的集合范围,泛型必须落在这个范围内,那么Integer及其子类都可以被看作为
泛型的子类型,插入到集合中

但是取出的时候遭罪了 ,不清楚泛型具体是什么类型,可能是List<Number>也可能List<Object>,
不知道用什么类型来接取出的数据,所以只能取出object类型

如果我们既希望读取, 也希望写入, 那么我们就必须明确地声明泛型参数的类型, 例如 List.

泛型的问题总结
下面这段基本都是摘抄自 Java泛型详解,我觉得写得非常好,所以粘贴过来

public class Node<T> {

private T data;
private Node<T> next;

public Node(T data, Node<T> next) }
this.data = data;
this.next = next;
}

public T getData() { return data; }
// ...
}
编译器做完相应的类型检查之后,实际上到了运行期间上面这段代码实际上将转换成:

public class Node {

private Object data;
private Node next;

public Node(Object data, Node next) {
this.data = data;
this.next = next;
}

public Object getData() { return data; }
// ...
}
这意味着不管我们声明Node还是Node,到了运行期间,JVM统统视为Node。有没有什么办法可以解决这个问题呢?这就需要我们自己重新设置bounds了,将上面的代码修改成下面这样:

public class Node<T extends Comparable<T>> {

private T data;
private Node<T> next;

public Node(T data, Node<T> next) {
this.data = data;
this.next = next;
}

public T getData() { return data; }
// ...
}
这样编译器就会将T出现的地方替换成Comparable而不再是默认的Object了:

public class Node {

private Comparable data;
private Node next;

public Node(Comparable data, Node next) {
this.data = data;
this.next = next;
}

public Comparable getData() { return data; }
// ...
}
上面的概念或许还是比较好理解,但其实泛型擦除带来的问题远远不止这些,接下来我们系统地来看一下类型擦除所带来的一些问题,有些问题在C++的泛型中可能不会遇见,但是在Java中却需要格外小心。

问题一
在Java中不允许创建泛型数组,类似下面这样的做法编译器会报错:


List<Integer>[] arrayOfLists = new List<Integer>[2]; // compile-time error
1
2
为什么编译器不支持上面这样的做法呢?继续使用逆向思维,我们站在编译器的角度来考虑这个问题。

我们先来看一下下面这个例子:

Object[] strings = new String[2];
strings[0] = "hi"; // OK
strings[1] = 100; // An ArrayStoreException is thrown.
对于上面这段代码还是很好理解,字符串数组不能存放整型元素,而且这样的错误往往要等到代码运行的时候才能发现,编译器是无法识别的。接下来我们再来看一下假设Java支持泛型数组的创建会出现什么后果:

Object[] stringLists = new List<String>[]; // compiler error, but pretend it's allowed
stringLists[0] = new ArrayList<String>(); // OK
// An ArrayStoreException should be thrown, but the runtime can't detect it.
stringLists[1] = new ArrayList<Integer>();
假设我们支持泛型数组的创建,由于运行时期类型信息已经被擦除,JVM实际上根本就不知道new ArrayList()和new ArrayList()的区别。类似这样的错误假如出现才实际的应用场景中,将非常难以察觉。

如果你对上面这一点还抱有怀疑的话,可以尝试运行下面这段代码:

public class ErasedTypeEquivalence {
public static void main(String[] args) {
Class c1 = new ArrayList<String>().getClass();
Class c2 = new ArrayList<Integer>().getClass();
System.out.println(c1 == c2); // true
}
}
问题二
继续复用我们上面的Node的类,对于泛型代码,Java编译器实际上还会偷偷帮我们实现一个Bridge method。

public class Node<T> {

public T data;

public Node(T data) { this.data = data; }

public void setData(T data) {
System.out.println("Node.setData");
this.data = data;
}
}

public class MyNode extends Node<Integer> {
public MyNode(Integer data) { super(data); }

public void setData(Integer data) {
System.out.println("MyNode.setData");
super.setData(data);
}
}
看完上面的分析之后,你可能会认为在类型擦除后,编译器会将Node和MyNode变成下面这样:

public class Node {

public Object data;

public Node(Object data) { this.data = data; }

public void setData(Object data) {
System.out.println("Node.setData");
this.data = data;
}
}

public class MyNode extends Node {

public MyNode(Integer data) { super(data); }

public void setData(Integer data) {
System.out.println("MyNode.setData");
super.setData(data);
}
}
实际上不是这样的,我们先来看一下下面这段代码,这段代码运行的时候会抛出ClassCastException异常,提示String无法转换成Integer:


MyNode mn = new MyNode(5);
Node n = mn; // A raw type - compiler throws an unchecked warning
n.setData("Hello"); // Causes a ClassCastException to be thrown.
// Integer x = mn.data;
如果按照我们上面生成的代码,运行到第3行的时候不应该报错(注意我注释掉了第4行),因为MyNode中不存在setData(String data)方法,所以只能调用父类Node的setData(Object data) 方法,既然这样上面的第3行代码不应该报错,因为String当然可以转换成Object了,那ClassCastException到底是怎么抛出的?

实际上Java编译器对上面代码自动还做了一个处理:

class MyNode extends Node {

// Bridge method generated by the compiler
public void setData(Object data) {
setData((Integer) data);
}

public void setData(Integer data) {
System.out.println("MyNode.setData");
super.setData(data);
}

// ...
}
这也就是为什么上面会报错的原因了,setData((Integer) data);的时候String无法转换成Integer。所以上面第2行编译器提示unchecked warning的时候,我们不能选择忽略,不然要等到运行期间才能发现异常。如果我们一开始加上Node n = mn就好了,这样编译器就可以提前帮我们发现错误。

问题三
正如我们上面提到的,Java泛型很大程度上只能提供静态类型检查,然后类型的信息就会被擦除,所以像下面这样利用类型参数创建实例的做法编译器不会通过

public static <E> void append(List<E> list) {
E elem = new E(); // compile-time error
list.add(elem);
}
但是如果某些场景我们想要需要利用类型参数创建实例,我们应该怎么做呢?可以利用反射解决这个问题:

public static <E> void append(List<E> list, Class<E> cls) throws Exception {
E elem = cls.newInstance(); // OK
list.add(elem);
}
我们可以像下面这样调用:

List<String> ls = new ArrayList<>();
append(ls, String.class);

实际上对于上面这个问题,还可以采用Factory和Template两种设计模式解决,感兴趣的朋友不妨去看一下Thinking in Java中第15章中关于Creating instance of types(英文版第664页)的讲解,这里我们就不深入了。

问题四
我们无法对泛型代码直接使用instanceof关键字,因为Java编译器在生成代码的时候会擦除所有相关泛型的类型信息,正如我们上面验证过的JVM在运行时期无法识别出ArrayList和ArrayList的之间的区别:

public static <E> void rtti(List<E> list) {
if (list instanceof ArrayList<Integer>) { // compile-time error
// ...
}
}

=> { ArrayList<Integer>, ArrayList<String>, LinkedList<Character>, ... }

和上面一样,我们可以使用通配符重新设置bounds来解决这个问题:

public static void rtti(List<?> list) {
if (list instanceof ArrayList<?>) { // OK; instanceof requires a reifiable type
// ...
}
}
我还多试了一种
List<Integer> list = new ArrayList<>();

if(list instanceof ArrayList<? extends Number>) { //也会报错,根据提示只能用ArrayList<?>才不会报错

}
————————————————
版权声明:本文为CSDN博主「whp404」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/whp404/article/details/95597614

一. Java 之泛型通配符 ? extends T 与 ? super T 解惑

简述

大家在平时的工作学习中, 肯定会见过不少如下的语句:

  1.  
    List<? super T>
  2.  
    List<? extends T>

我们都知道, 上面的代码时关于 Java 泛型的, 那么这两个不同的写法都有什么区别呢?

首先, 说到 Java 的泛型, 我们必须要提到的是Java 泛型的类型擦除机制: Java中的泛型基本上都是在编译器这个层次来实现的. 在生成的 Java 字节代码中是不包含泛型中的类型信息的. 使用泛型的时候加上的类型参数, 会被编译器在编译的时候去掉. 这个过程就称为类型擦除. 如在代码中定义的List<Object>和List<String>等类型, 在编译之后都会变成List, JVM看到的只是List, 而由泛型附加的类型信息对JVM来说是不可见的.

在使用泛型类时, 我们可以使用一个具体的类型, 例如可以定义一个 List<Integer> 的对象, 我们的泛型参数就是 Integer; 我们也可以使用通配符 ? 来表示一个未知类型, 例如 List<?> 就表示了泛型参数是某个类型, 只不过我们并不知道它的具体类型时什么.
List<?>所声明的就是所有类型都是可以的, 但需要注意的是, List<?>并不等同于List<Object>. 对于 List<Object> 来说, 它实际上确定了 List 中包含的是 Object 及其子类, 我们可以使用 Object 类型来接收它的元素. 相对地, List<?> 则表示其中所包含的元素类型是不确定, 其中可能包含的是 String, 也可能是 Integer. 如果它包含了 String 的话, 往里面添加 Integer 类型的元素就是错误的. 作为对比, 我们可以给一个 List<Object> 添加 String 元素, 也可以添加 Integer 类型的元素, 因为它们都是 Object 的子类.
正因为类型未知, 我们就不能通过 new ArrayList<?>() 的方法来创建一个新的ArrayList 对象, 因为编译器无法知道具体的类型是什么. 但是对于 List<?> 中的元素, 我们却都可以使用 Object 来接收, 因为虽然类型未知, 但肯定是Object及其子类.

我们在上面提到了, List<?> 中的元素只能使用 Object 来引用, 这样作肯定时不太方便的, 不过幸运的是, Java 的泛型机制允许我们对泛型参数的类型的上界和下界做一些限制, 例如 List<? extends Number> 定义了泛型的上界是 Number, 即 List 中包含的元素类型是 Number 及其子类. 而 List<? super Number> 定义了泛型的下界, 即 List 中包含的是 Number 及其父类.
当引入了泛型参数的上界和下界后, 我们编写代码相对来说就方便了许多, 不过也引入了新的问题, 即我们在什么时候使用上界, 什么时候使用下界, 以及它们的区别和限制到底时什么? 下面我来说说我的理解.

? extends T

? extends T 描述了通配符上界, 即具体的泛型参数需要满足条件: 泛型参数必须是 T 类型或它的子类, 例如:

  1.  
    List<? extends Number> numberArray = new ArrayList<Number>(); // Number 是 Number 类型的
  2.  
    List<? extends Number> numberArray = new ArrayList<Integer>(); // Integer 是 Number 的子类
  3.  
    List<? extends Number> numberArray = new ArrayList<Double>(); // Double 是 Number 的子类

上面三个操作都是合法的, 因为 ? extends Number 规定了泛型通配符的上界, 即我们实际上的泛型必须要是 Number 类型或者是它的子类, 而 Number, Integer, Double 显然都是 Number 的子类(类型相同的也可以, 即这里我们可以认为 Number 是 Number 的子类).

子类型判断

假设有类型 G, 以及 SuperClass 和 SubClass 两个类, 并且 SuperClass 是 SubClass 的父类, 那么:

  • G<? extends SubClass> 是 G<? extends SuperClass> 的子类型. 如 List<? extends Integer> 是 List<? extends Number> 的子类型
  • G<SuperClass> 是 G<? extends SuperClass> 的子类型, 例如 List<Integer> 是 List<? extends Integer> 的子类型.
  • G<?> 和 G<? extends Object> 等同.

可以想象 G<? extends T> 为一个左闭右开的区间(T 在最左边), G<? extends Object> 是最大的区间, 当区间 G<? extends SuperClass> 包含 区间 G<? extends SubClass>时, 那么较大的区间就是父类.

关于读取

根据上面的例子, 对于 List<? extends Number> numberArray 对象:

  • 我们能够从 numberArray 中读取到 Number 对象, 因为 numberArray 中包含的元素是 Number 类型或 Number 的子类型.
  • 我们不能从 numberArray 中读取到 Integer 类型, 因为 numberArray 中可能保存的是 Double 类型.
  • 同理, 我们也不能从 numberArray 中读取到 Double 类型.

关于写入

根据上面的例子, 对于 List<? extends Number> numberArray 对象:

  • 我们不能添加 Number 到 numberArray 中, 因为 numberArray 有可能是List<Double> 类型
  • 我们不能添加 Integer 到 numberArray 中, 因为 numberArray 有可能是 List<Double> 类型
  • 我们不能添加 Double 到 numberArray 中, 因为 numberArray 有可能是 List<Integer> 类型

即, 我们不能添加任何对象到 List<? extends T> 中, 因为我们不能确定一个 List<? extends T> 对象实际的类型是什么, 因此就不能确定插入的元素的类型是否和这个 List 匹配. List<? extends T> 唯一能保证的是我们从这个 list 中读取的元素一定是一个 T 类型的.

? super T

? super T 描述了通配符下界, 即具体的泛型参数需要满足条件: 泛型参数必须是 T 类型或它的父类, 例如:

  1.  
    // 在这里, Integer 可以认为是 Integer 的 "父类"
  2.  
    List<? super Integer> array = new ArrayList<Integer>();
  3.  
    // Number 是 Integer 的 父类
  4.  
    List<? super Integer> array = new ArrayList<Number>();
  5.  
    // Object 是 Integer 的 父类
  6.  
    List<? super Integer> array = new ArrayList<Object>();

关于读取

对于上面的例子中的 List<? super Integer> array 对象:

  • 我们不能保证可以从 array 对象中读取到 Integer 类型的数据, 因为 array 可能是 List<Number> 类型的.
  • 我们不能保证可以从 array 对象中读取到 Number 类型的数据, 因为 array 可能是 List<Object> 类型的.
  • 唯一能够保证的是, 我们可以从 array 中获取到一个 Object 对象的实例.

关于写

对于上面的例子中的 List<? super Integer> array 对象:

  • 我们可以添加 Integer 对象到 array 中, 也可以添加 Integer 的子类对象到 array 中.
  • 我们不能添加 Double/Number/Object 等不是 Integer 的子类的对象到 array 中.

易混淆点

有一点需要注意的是, List<? super T> 和 List<? extends T> 中, 我们所说的 XX 是 T 的父类(a superclass of T) 或 XX 是 T 的子类(a subclass of T) 其实是针对于泛型参数而言的. 例如考虑如下例子:

  1.  
    List<? super Integer> l1 = ...
  2.  
    List<? extends Integer> l2 = ...

那么这里 ? super Integer 和 ? extends Integer 的限制是对谁的呢? 是表示我们可以插入任意的对象 X 到 l1 中, 只要 X 是 Integer 的父类? 是表示我们可以插入任意的对象 Y 到 l2 中, 只要 Y 是 Integer 的子类?
其实不是的, 我们必须要抛弃上面的概念, ? super Integer 和 ? extends Integer 限制的其实是 泛型参数, 即 List<? super Integer> l1 表示 l1 的泛型参数 T 必须要满足 T 是 Integer 的父类, 因此诸如 List<Object>List<Number 的对象就可以赋值到 l1 中. 正因为我们知道了 l1 中的泛型参数的边界信息, 因此我们就可以向 l1 中添加 Integer 对象了, 推理过程如下:

  1.  
    令 T 是 l1 的泛型参数, 即:
  2.  
    l1 = List<T> = List<? super Integer>
  3.  
    因此有 T 是 Integer 或 Integer 的父类.
  4.  
    如果 T 是 Integer, 则 l1 = List<Integer>, 显然我们可以添加任意的 Integer 对象或 Integer 的子类对象到 l1 中.
  5.  
    如果 T 是 Integer 的父类, 那么同理, 对于 Integer 或 Integer 的子类的对象, 我们也可以添加到 l1 中.

按同样的分析方式, List<? extends Integer> l2 表示的是 l2 的泛型参数是 Integer 的子类型. 而如果我们要给一个 List<T> 插入一个元素的话, 我们需要保证此元素是 T 或是 T 的子类, 而这里 List<? extends Integer> l2, l2 的泛型参数是什么类型我们都不知道, 进而就不能确定 l2 的泛型参数的子类是哪些, 因此我们就不能向 l2 中添加任何的元素了.

来一个对比:

  • 对于 List<? super Integer> l1:
  • 正确的理解: ? super Integer 限定的是泛型参数. 令 l1 的泛型参数是 T, 则 T 是 Integer 或 Integer 的子类, 因此 Integer 或 Integer 的子类的对象就可以添加到 l1 中.
  • 错误的理解: ? super Integer限定的是插入的元素的类型, 因此只要是 Integer 或 Integer 的父类的对象都可以插入 l1 中
  • 对于 List<? extends Integer> l2:
  • 正确的理解: ? extends Integer 限定的是泛型参数. 令 l2 的泛型参数是 T, 则 T 是 Integer 或 Integer 的子类, 进而我们就不能找到一个类 X, 使得 X 是泛型参数 T 的子类, 因此我们就不可以向 l2 中添加元素. 不过由于我们知道了泛型参数 T 是 Integer 或 Integer 的子类这一点, 因此我们就可以从 l2 中读取到元素, 并可以存放到 Integer 中.
  • 错误的理解: ? extends Integer 限定的是插入元素的类型, 因此只要是 Integer 或 Integer 的子类的对象都可以插入 l2 中

使用场景

PECE 原则: Producer Extends, Consumer Super

  • Producer extends: 如果我们需要一个 List 提供类型为 T 的数据(即希望从 List 中读取 T 类型的数据), 那么我们需要使用 ? extends T, 例如 List<? extends Integer>. 但是我们不能向这个 List 添加数据.
  • Consumer Super: 如果我们需要一个 List 来消费 T 类型的数据(即希望将 T 类型的数据写入 List 中), 那么我们需要使用 ? super T, 例如 List<? super Integer>. 但是这个 List 不能保证从它读取的数据的类型.
  • 如果我们既希望读取, 也希望写入, 那么我们就必须明确地声明泛型参数的类型, 例如 List<Integer>.

例子:

  1.  
    public class Collections {
  2.  
    public static <T> void copy(List<? super T> dest, List<? extends T> src)
  3.  
    {
  4.  
    for (int i=0; i<src.size(); i++)
  5.  
    dest.set(i,src.get(i));
  6.  
    }
  7.  
    }

上面的例子是一个拷贝数据的代码, src 是 List<? extends T> 类型的, 因此它可以读取出 T 类型(或其子类)的数据, dest 是 List<? super T> 类型的, 因此它可以写入 T 类型或其父类的数据.

posted on 2022-04-28 15:02  左手指月  阅读(85)  评论(0编辑  收藏  举报