泛型中? 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关键字,以及在使用这两个关键字的时候为什 ...
随机推荐
- 工作中碰到的js问题(disabled表单元素不能提交到服务器)
今天碰到一个奇葩的问题,asp页面表单提交后,有一个文本框<input type="text" name="phone" id="phone&q ...
- android 切换fragment的两种方式
使用add方法切换时:载入Fragment1Fragment1 onCreateFragment1 onCreateViewFragment1 onStartFragment1 onResume用以下 ...
- Android工程师常见面试题集答案
13.描述一下Android的系统结构? android系统架构分从下往上为linux 内核层.运行库.应用程序框架层.和应用程序层. linuxkernel:负责硬件的驱动程序.网络.电源.系统安全 ...
- C语言笔记
.c是C语言源文件,在编写代码的时候创建 .o是目标文件,在编译成功的时候产生 .obj .out是可执行文件,在链接成功的时候产生 工具:clang编译器(Xcode3 gcc Xcode4 LL ...
- 【代码笔记】iOS-竖状图
一,效果图. 二,工程图. 三,代码. RootViewController.h #import <UIKit/UIKit.h> @interface RootViewController ...
- iOS中UINavigationController控制器使用详解
一.概述 UINavigationController用来管理视图控制器,在多视图控制器中常用.它以栈的形式管理视图控制器,管理视图控制器个数理论上不受限制(实际受内存限制),push和pop方法来弹 ...
- python 打印对象的所有属性值
def prn_obj(obj): print '\n'.join(['%s:%s' % item for item in obj.__dict__.items()])
- 一次进程hang住问题分析。。。
这两天有同学使用数据校验工具时发现进程hang住了,也不知道什么原因,我简单看了看进程堆栈,问题虽然很简单,但能导致程序hang住,也一定不是小问题.简单说明下程序组件的结构,程序由两部分构成,dbc ...
- 五、Android学习第四天补充——Android的常用控件(转)
(转自:http://wenku.baidu.com/view/af39b3164431b90d6c85c72f.html) 五.Android学习第四天补充——Android的常用控件 熟悉常用的A ...
- python enumerate函数用法
enumerate函数用于遍历序列中的元素以及它们的下标 i = 0 seq = ['one', 'two', 'three'] for element in seq: print i, seq[i] ...