泛型中? super T和? extends T的区别
原文出处: 并发编程网
经常发现有List<? super T>、Set<? extends T>的声明,是什么意思呢?<? super T>表示包括T在内的任何T的父类,<? extends T>表示包括T在内的任何T的子类,下面我们详细分析一下两种通配符具体的区别。
extends
List<? extends Number> foo3的通配符声明,意味着以下的赋值是合法的:
01 |
// Number "extends" Number (in this context) |
02 |
03 |
List<? extends Number> foo3 = new ArrayList<? extends Number>(); |
04 |
05 |
// Integer extends Number |
06 |
07 |
List<? extends Number> foo3 = new ArrayList<? extends Integer>(); |
08 |
09 |
// Double extends Number |
10 |
11 |
List<? extends Number> foo3 = new ArrayList<? extends Double>(); |
- 读取操作通过以上给定的赋值语句,你一定能从foo3列表中读取到的元素的类型是什么呢?你可以读取到Number,因为以上的列表要么包含Number元素,要么包含Number的类元素。
你不能保证读取到Integer,因为foo3可能指向的是List<Double>。
你不能保证读取到Double,因为foo3可能指向的是List<Integer>。
- 写入操作过以上给定的赋值语句,你能把一个什么类型的元素合法地插入到foo3中呢?
你不能插入一个Integer元素,因为foo3可能指向List<Double>。
你不能插入一个Double元素,因为foo3可能指向List<Integer>。
你不能插入一个Number元素,因为foo3可能指向List<Integer>。
你不能往List<? extends T>中插入任何类型的对象,因为你不能保证列表实际指向的类型是什么,你并不能保证列表中实际存储什么类型的对象。唯一可以保证的是,你可以从中读取到T或者T的子类。
super
现在考虑一下List<? super T>。
List<? super Integer> foo3的通配符声明,意味着以下赋值是合法的:
01 |
// Integer is a "superclass" of Integer (in this context) |
02 |
03 |
List<? super Integer> foo3 = new ArrayList<Integer>(); |
04 |
05 |
// Number is a superclass of Integer |
06 |
07 |
List<? super Integer> foo3 = new ArrayList<Number>(); |
08 |
09 |
// Object is a superclass of Integer |
10 |
11 |
List<? super Integer> foo3 = new ArrayList<Object>(); |
- 读取操作通过以上给定的赋值语句,你一定能从foo3列表中读取到的元素的类型是什么呢?你不能保证读取到Integer,因为foo3可能指向List<Number>或者List<Object>。
你不能保证读取到Number,因为foo3可能指向List<Object>。
唯一可以保证的是,你可以读取到Object或者Object子类的对象(你并不知道具体的子类是什么)。
- 写入操作通过以上给定的赋值语句,你能把一个什么类型的元素合法地插入到foo3中呢?你可以插入Integer对象,因为上述声明的列表都支持Integer。
你可以插入Integer的子类的对象,因为Integer的子类同时也是Integer,原因同上。
你不能插入Double对象,因为foo3可能指向ArrayList<Integer>。
你不能插入Number对象,因为foo3可能指向ArrayList<Integer>。
你不能插入Object对象,因为foo3可能指向ArrayList<Integer>。
PECS
请记住PECS原则:生产者(Producer)使用extends,消费者(Consumer)使用super。
- 生产者使用extends
如果你需要一个列表提供T类型的元素(即你想从列表中读取T类型的元素),你需要把这个列表声明成<? extends T>,比如List<? extends Integer>,因此你不能往该列表中添加任何元素。
- 消费者使用super
如果需要一个列表使用T类型的元素(即你想把T类型的元素加入到列表中),你需要把这个列表声明成<? super T>,比如List<? super Integer>,因此你不能保证从中读取到的元素的类型。
- 即是生产者,也是消费者
如果一个列表即要生产,又要消费,你不能使用泛型通配符声明列表,比如List<Integer>。
例子
请参考java.util.Collections里的copy方法(JDK1.7):

引用例子:
泛型中使用通配符有两种形式:子类型限定<? extends xxx>和超类型限定<? super xxx>。
(1)子类型限定
下面的代码定义了一个Pair<T>类,以及Employee,Manager和President类。
- public class Pair<T> {
- private T first;
- private T second;
- public Pair(T first, T second) {
- this.first = first;
- this.second = second;
- }
- public T getFirst() {
- return first;
- }
- public T getSecond() {
- return second;
- }
- public void setFirst(T newValue) {
- first = newValue;
- }
- public void setSecond(T newValue) {
- second = newValue;
- }
- }
- class Employee {
- private String name;
- private double salary;
- public Employee(String n, double s) {
- name = n;
- salary = s;
- }
- public String getName() {
- return name;
- }
- public double getSalary() {
- return salary;
- }
- }
- class Manager extends Employee {
- public Manager(String n, double s) {
- super(n, s);
- }
- }
- <pre name="code" class="java">
- class President extends Manager {
- public President(String n, double s) {
- super(n, s);
- }
- }
现在要定义一个函数可以打印Pair<Employee>
- public static void printEmployeeBoddies(Pair<Employee> pair) {
- System.out.println(pair.getFirst().getName() + ":" + pair.getSecond().getName());
- }
可是有一个问题是这个函数输入参数只能传递类型Pair<Employee>,而不能传递Pair<Manager>和Pair<President>。例如下面的代码会产生编译错误
- Manager mgr1 = new Manager("Jack", 10000.99);
- Manager mgr2 = new Manager("Tom", 10001.01);
- Pair<Manager> managerPair = new Pair<Manager>(mgr1, mgr2);
- PairAlg.printEmployeeBoddies(managerPair);
之所以会产生编译错误,是因为Pair<Employee>和Pair<Manager>实际上是两种类型。
由上图可以看出,类型Pair<Manager>是类型Pair<? extends Employee>的子类型,所以为了解决这个问题可以把函数定义改成
public static void printEmployeeBoddies(Pair<? extends Employee> pair)
但是使用通配符会不会导致通过Pair<? extends Employee>的引用破坏Pair<Manager>对象呢?例如:
Pair<? extends Employee> employeePair = managePair;employeePair.setFirst(new Employee("Tony", 100));
不用担心,编译器会产生一个编译错误。Pair<? extends Employee>参数替换后,我们得到如下代码
? extends Employee getFirst()
void setFirst(? extends Employee)
对于get方法,没问题,因为编译器知道可以把返回对象转换为一个Employee类型。但是对于set方法,编译器无法知道具体的类型,所以会拒绝这个调用。
(2)超类型限定
超类型限定和子类型限定相反,可以给方法提供参数,但是不能使用返回值。? super Manager这个类型限定为Manager的所有超类。
Pair<? super Manager>参数替换后,得到如下方法
? super Manager getFirst()
void setFirst(? super Manager)
编译器可以用Manager的超类型,例如Employee,Object来调用setFirst方法,但是无法调用getFirst,因为不能把Manager的超类引用转换为Manager引用。
超类型限定的存在是为了解决下面一类的问题。例如要写一个函数从Manager[]中找出工资最高和最低的两个,放在一个Pair中返回。
- public static void minMaxSal(Manager[] mgrs, Pair<? super Manager> pair) {
- if (mgrs == null || mgrs.length == 0) {
- return;
- }
- pair.setFirst(mgrs[0]);
- pair.setSecond(mgrs[0]);
- //TODO
- }
如此就可以这样调用
- Pair<? super Manager> pair = new Pair<Employee>(null, null);
- minMaxSal(new Manager[] {mgr1, mgr2}, pair);
泛型中? super T和? extends T的区别的更多相关文章
- java泛型中? super T和? extends T的区别
<? super T>表示包括T在内的任何T的父类,<? extends T>表示包括T在内的任何T的子类;请记住PECS原则:生产者(Producer)使用extends,消 ...
- java泛型中<? super String>和<? extends String> 的区别
(1)<? super String> is any class which is a superclass of String (including String itself). (I ...
- JAVA泛型中的有界类型(extends super)(转)
JDK1.5中引入了泛型(Generic)机制.泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数.这种参数类型可以用在类.接口和方法的创建中,分别称为泛型类.泛型接口.泛型方法. Ja ...
- python中super().__init__和类名.__init__的区别
super().__init__相对于类名.__init__,在单继承上用法基本无差 但在多继承上有区别,super方法能保证每个父类的方法只会执行一次,而使用类名的方法会导致方法被执行多次 多继承时 ...
- AJPFX详解泛型中super和extends关键字
首先,我们定义两个类,A和B,并且假设B继承自A.下面的代码中,定义了几个静态泛型方法,这几个例子随便写的,并不是特别完善,我们主要考量编译失败的问题: Java代码 public class Ge ...
- [ 原创 ] Java基础1--Java中super和this的用法和区别
许多同学在学习Java时分不清楚this和super的用法和区别,今天偶然发现一片加精的博文,看完内容准备自己也写下来积累一下 1.如果想在子类的构造方法中调用父类的构造方法,必须在子类的构造方法中使 ...
- Java泛型中extends和super的理解(转)
E – Element (在集合中使用,因为集合中存放的是元素) T – Type(Java 类) K – Key(键) V – Value(值) N – Number(数值类型) ? – 表示不确定 ...
- Java泛型中<? extends E>和<? super E>的区别
这篇文章谈一谈Java泛型声明<? extends E>和<? super E>的作用和区别 <? extends E> <? extends E> 是 ...
- 浅谈Java泛型中的extends和super关键字
泛型是在Java 1.5中被加入了,这里不讨论泛型的细节问题,这个在Thinking in Java第四版中讲的非常清楚,这里要讲的是super和extends关键字,以及在使用这两个关键字的时候为什 ...
随机推荐
- Android Builder模式在开发中的应用
最近在学习图片加载框架Glide的时候,被他精简的写法震惊了.一句话,就可以搞定. Glide.with(mContext) .load(url) .centerCrop() .placeholder ...
- Android-Universal-Image-Loader 框架使用
1.Android-Universal-Image-Loader github下载地址 https://github.com/nostra13/Android-Universal-Image ...
- 【CoreData】多个数据库使用
在实际开发中,往往需要每个模块使用不同数据库,而CoreData也具备这样的功能,使用起来也很方便: 首先我们创建2个模型文件(School和Educationist) // 1.创建模型文件 (相当 ...
- UIPickerView简单应用
下面是一些效果图 下面是代码.有些枯燥 , 其实并不难 . #import <UIKit/UIKit.h> @interface ViewController : UIViewContro ...
- Nodejs——包与NPM
在模块之外,包和NPM则是将模块联系起来的一种机制. CommonJS的包规范由包结构和包描述文件组成. 包实际上是一个存档文件,即一个目录直接打包为.zip或tar.gz格式的文件. 完全符合Com ...
- Java 往年试卷参考答案!!!
仅供参考: 第一题: E C E A D D C A C A C A B A B C C D B C 第二题: True True False 11 12 13 14 No such file fou ...
- WPF学习之路(八)页面
传统的应用程序中有两类应用程序模式:桌面应用,Web应用.WPF的导航应用程序模糊了这两类应用程序的界限的第三类应用程序 WPF导航表现为两种形式,一是将导航内容寄宿于窗口,二是XAML浏览器应用程序 ...
- ORACLE数据库异步IO介绍
异步IO概念 Linux 异步 I/O (AIO)是 Linux 内核中提供的一个增强的功能.它是Linux 2.6 版本内核的一个标准特性,当然我们在2.4 版本内核的补丁中也可以找到它.AIO 背 ...
- Sql Server之旅——第六站 使用winHex利器加深理解数据页
这篇我来介绍一个winhex利器,这个工具网上有介绍,用途大着呢,可以用来玩数据修复,恢复删除文件等等....它能够将一个file解析成 hex形式,这样你就可以对hex进行修改,然后你就可以看到修复 ...
- Python引用模块和查找模块路径
模块间相互独立相互引用是任何一种编程语言的基础能力.对于"模块"这个词在各种编程语言中或许是不同的,但我们可以简单认为一个程序文件是一个模块,文件里包含了类或者方法的定义.对于编译 ...