你们在平时的工做学习中, 确定会见过很多以下的语句:java
List<? super T> List<? extends T>
咱们都知道, 上面的代码时关于 Java 泛型的, 那么这两个不一样的写法都有什么区别呢?学习
首先, 说到 Java 的泛型, 咱们必需要提到的是Java 泛型的类型擦除机制: Java中的泛型基本上都是在编译器这个层次来实现的. 在生成的 Java 字节代码中是不包含泛型中的类型信息的. 使用泛型的时候加上的类型参数, 会被编译器在编译的时候去掉. 这个过程就称为类型擦除. 如在代码中定义的List<Object>和List<String>等类型, 在编译以后都会变成List, JVM看到的只是List, 而由泛型附加的类型信息对JVM来讲是不可见的.code
在使用泛型类时, 咱们可使用一个具体的类型, 例如能够定义一个 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 及其父类.
当引入了泛型参数的上界和下界后, 咱们编写代码相对来讲就方便了许多, 不过也引入了新的问题, 即咱们在何时使用上界, 何时使用下界, 以及它们的区别和限制到底时什么? 下面我来讲说个人理解.get
? 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 的子类
上面三个操做都是合法的, 由于 ? extends Number 规定了泛型通配符的上界, 即咱们实际上的泛型必需要是 Number 类型或者是它的子类, 而 Number, Integer, Double 显然都是 Number 的子类(类型相同的也能够, 即这里咱们能够认为 Number 是 Number 的子类).io
假设有类型 G, 以及 SuperClass 和 SubClass 两个类, 而且 SuperClass 是 SubClass 的父类, 那么:编译
G<? extends SubClass> 是 G<? extends SuperClass> 的子类型. 如 List<? extends Integer> 是 List<? extends Number> 的子类型class
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 描述了通配符下界, 即具体的泛型参数须要知足条件: 泛型参数必须是 T 类型或它的父类
, 例如:
// 在这里, Integer 能够认为是 Integer 的 "父类" List<? super Integer> array = new ArrayList<Integer>(); // Number 是 Integer 的 父类 List<? super Integer> array = new ArrayList<Number>(); // Object 是 Integer 的 父类 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)
实际上是针对于泛型参数而言的. 例如考虑以下例子:
List<? super Integer> l1 = ... 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 对象了, 推理过程以下:
令 T 是 l1 的泛型参数, 即: l1 = List<T> = List<? super Integer> 所以有 T 是 Integer 或 Integer 的父类. 若是 T 是 Integer, 则 l1 = List<Integer>, 显然咱们能够添加任意的 Integer 对象或 Integer 的子类对象到 l1 中. 若是 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 或 Integer 的子类), 并能够存放到 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>.
例子:
public class Collections { public static <T> void copy(List<? super T> dest, List<? extends T> src) { for (int i=0; i<src.size(); i++) dest.set(i,src.get(i)); } }
上面的例子是一个拷贝数据的代码, src 是 List<? extends T> 类型的, 所以它能够读取出 T 类型的数据(读取的数据类型是 T 或是 T 的子类, 可是咱们不能确切的知道它是什么类型, 惟一能肯定的是读取的类型 is instance of T), , dest 是 List<? super T> 类型的, 所以它能够写入 T 类型或其子类的数据.
Java深度历险(五)——Java泛型
difference-between-super-t-and-extends-t-in-java