浅谈Java泛型中的extends和super关键字
泛型是在Java 1.5中被加入了,这里不讨论泛型的细节问题,这个在Thinking in Java第四版中讲的非常清楚,这里要讲的是super和extends关键字,以及在使用这两个关键字的时候为什么会不同的限制。
首先,我们定义两个类,A和B,并且假设B继承自A。
package com.wms.test; import java.util.ArrayList;
import java.util.List; public class Generic {
public static void main(String[] args){
List<? extends A> list1 = new ArrayList<A>();
// list1.add(new A()); //错误,编译器无法确定List所持有的类型,所以无法安全的向其中添加对象
A a = list1.get(0);
List<? extends A> list2 = new ArrayList<B>();
List<? super B> list3 = new ArrayList<B>();
list3.add(new B());
//想要正确,必须向下转型,但是向下转型是不安全的,非常容易出错
// B b = list3.get(0); //编译器无法确定get返回的对象类型是B,还是B的父类或 Object.
List<? super B> list4 = new ArrayList<A>();
}
} class A{}
class B extends A{}
从上面这段创建List的代码我们就更加容易理解super和extends关键字的含义了。首先要说明的一点是,Java强制在创建对象的时候必须给类型参数制定具体的类型,不能使用通配符,也就是说new ArrayList<? extends A>(),new ArrayList<?>()这种形式的初始化语句是不允许的。
从上面main函数的第一行和第二行,我们可以理解extends的含义,在创建ArrayList的时候,我们可以指定A或者B作为具体的类型。也就是,如果<? extends X>,那么在创建实例的时候,我们就可以用X或者扩展自X的类为泛型参数来作为具体的类型,也可以理解为给"?"号指定具体类型,这就是extends 的含义。
同样的,第三行和第四行就说明,如果<? super X>,那么在创建实例的时候,我们可以指定X或者X的任何的超类来作为泛型参数的具体类型。
当我们使用List<? extends X>这种形式的时候,调用List的add方法会导致编译失败,因为我们在创建具体实例的时候,可能是使用了X也可能使用了X的子类,而这个信息编译器是没有办法知道的,同时,对于ArrayList<T>来说,只能放一种类型的对象。这就是问题的本质。而对于get方法来说,由于我们是通过X或者X的子类来创建实例的,而用超类来引用子类在Java中是合法的,所以,通过get方法能够拿到一个X类型的引用,当然这个引用可以指向X也可以指向X的任何子类。
而当我们使用List<? super X>这种形式的时候,调用List的get方法会失败。因为我们在创建实例的时候,可能用了X也可能是X的某一个超类,那么当调用get的时候,编译器是无法准确知晓的。而调用add方法正好相反,由于我们使用X或者X的超类来创建的实例,那么向这个List中加入X或者X的子类肯定是没有问题的, 因为超类的引用是可以指向子类的。
最后还有一点,这两个关键字的出现都是因为Java中的泛型没有协变特性的导致的。
小结(此处还是没太明白,有待解决)
extends 可用于的返回类型限定,不能用于参数类型限定。
super 可用于参数类型限定,不能用于返回类型限定。
>带有super超类型限定的通配符可以向泛型对象中写入,带有extends子类型限定的通配符可以向泛型对象读取。
什么是PECS?
PECS指“Producer Extends,Consumer Super”。换句话说,如果参数化类型表示一个生产者,就使用<? extends T>;如果它表示一个消费者,就使用<? super T>,可能你还不明白,不过没关系,接着往下看好了。
下面是一个简单的Stack的API接口:
public interface Stack<E>{
public Stack();
public void push(E e):
public E pop();
public boolean isEmpty();
}
假设想增加一个方法,按顺序将一系列元素全部放入Stack中,你可能想到的实现方式如下:
public void pushAll(Iterable<E> iter) {
for(E e : iter)
push(e);
}
假设有个Stack<Number>,想要灵活的处理Integer,Long等Number的子类型的集合
Stack<Number> stack = new Stack<Number>();
Iterable<Integer> iter = null;
/*
* 此时代码编译无法通过,因为对于类型Number和Integer来说,虽然后者是Number的子类,
* 但是对于任意Number集合(如List<Number>)不是Integer集合(如List<Integer>)的超类,
* 因为泛型是不可变的。
*/
stack.pushAll(iter); //错误
此时代码编译无法通过,因为对于类型Number和Integer来说,虽然后者是Number的子类,但是对于任意Number集合(如List<Number>)不是Integer集合(如List<Integer>)的超类,因为泛型是不可变的。
幸好java提供了一种叫有限通配符的参数化类型,pushAll参数替换为“E的某个子类型的Iterable接口”:
public void pushAll01(Iterable<? extends E> iter) {
for(E e : iter)
push(e);
}
此时再这样调用:
/*
* 这样就可以正确编译了,这里的<? extends E>就是所谓的 producer-extends。
* 这里的Iterable就是生产者,要使用<? extends E>。
* 因为Iterable<? extends E>可以容纳任何E的子类。在执行操作时,可迭代对象的每个元素都可以当作是E来操作。
*/
stack.pushAll01(iter); //正确
这样就可以正确编译了,这里的<? extends E>就是所谓的 producer-extends。这里的Iterable就是生产者,要使用<? extends E>。因为Iterable<? extends E>可以容纳任何E的子类。在执行操作时,可迭代对象的每个元素都可以当作是E来操作。
与之对应的是:假设有一个方法popAll()方法,从Stack集合中弹出每个元素,添加到指定集合中去。
public void popAll(Collection<E> c) {
c.add(pop());
}
假设有一个Stack<Number>和Collection<Object>对象:
Stack<Number> stack = new Stack<Number>();
Collection<Object> c = null;
/*
* 该方法要正确,必须c为Collection<Number>,和上面同理
*/
stack.popAll(c); //错误
同样上面这段代码也无法通过,解决的办法就是使用Collection<? super E>。这里的objects是消费者,因为是添加元素到objects集合中去。使用Collection<? super E>后,无论objects是什么类型的集合,满足一点的是他是E的超类,所以不管这个参数化类型具体是什么类型都能将E装进objects集合中去。
public void popAll01(Collection<? super E> c) {
c.add(pop());
}
/*
* 同样上面这段代码也无法通过,解决的办法就是使用Collection<? super E>。
* 这里的objects是消费者,因为是添加元素到objects集合中去。
* 使用Collection<? super E>后,无论objects是什么类型的集合,
* 满足一点的是他是E的超类,所以不管这个参数化类型具体是什么类型都能将E装进objects集合中去。
*/
stack.popAll01(c);
综合代码:
package com.wms.test;
import java.util.Collection;
public class Generic {
public static void main(String[] args) {
Stack<Number> stack = new Stack<Number>();
Iterable<Integer> iter = null;
/*
* 此时代码编译无法通过,因为对于类型Number和Integer来说,虽然后者是Number的子类,
* 但是对于任意Number集合(如List<Number>)不是Integer集合(如List<Integer>)的超类,
* 因为泛型是不可变的。
*/
stack.pushAll(iter); //错误
/*
* 这样就可以正确编译了,这里的<? extends E>就是所谓的 producer-extends。
* 这里的Iterable就是生产者,要使用<? extends E>。
* 因为Iterable<? extends E>可以容纳任何E的子类。在执行操作时,可迭代对象的每个元素都可以当作是E来操作。
*/
stack.pushAll01(iter); //正确
Collection<Object> c = null;
/*
* 该方法要正确,必须c为Collection<Number>,和上面同理
*/
stack.popAll(c); //错误
/*
* 同样上面这段代码也无法通过,解决的办法就是使用Collection<? super E>。
* 这里的objects是消费者,因为是添加元素到objects集合中去。
* 使用Collection<? super E>后,无论objects是什么类型的集合,
* 满足一点的是他是E的超类,所以不管这个参数化类型具体是什么类型都能将E装进objects集合中去。
*/
stack.popAll01(c);
}
}
class Stack<E> {
public Stack(){
}
public void push(E e) {
}
public void pushAll(Iterable<E> iter) {
for(E e : iter)
push(e);
}
public void pushAll01(Iterable<? extends E> iter) {
for(E e : iter)
push(e);
}
public E pop() {
return null;
}
public void popAll(Collection<E> c) {
c.add(pop());
}
public void popAll01(Collection<? super E> c) {
c.add(pop());
}
}
浅谈Java泛型中的extends和super关键字的更多相关文章
- 浅谈Java泛型中的extends和super关键字(转)
通配符 在本文的前面的部分里已经说过了泛型类型的子类型的不相关性.但有些时候,我们希望能够像使用普通类型那样使用泛型类型: 向上造型一个泛型对象的引用 向下造型一个泛型对象的引用 向上造型一个泛型对象 ...
- Java泛型中的extends和super关键字
理解List<? extends T> list, T key, Comparator<? super T> c 这些一般用在方法形参类型上,用于接受泛型对象. 1.List& ...
- Java泛型(6):extends和super关键字
(1) <T extends A> 因为擦除移除了类型信息,而无界的泛型参数调用的方法只等同于Object.但是我们可以限定这个泛型参数为某个类型A的子集,这样泛型参数声明的引用就可以用类 ...
- 浅谈Java泛型中的? extends E和?super E
https://blog.csdn.net/zymx14/article/details/78073757
- Java泛型中<? extends E>和<? super E>的区别
这篇文章谈一谈Java泛型声明<? extends E>和<? super E>的作用和区别 <? extends E> <? extends E> 是 ...
- 浅谈JAVA GUI中,AWT与Swing的区别、联系及优缺点
浅谈JAVA GUI中,AWT与Swing的区别.联系及优缺点 A.区别 1.发布的时间 AWT是在JDK 1.0版本时提出的 Swing是在AWT之后提出的(JAVA 2) 2. ”重量” AWT是 ...
- Java泛型中<?> 和 <? extends Object>的异同分析
相信很多人和我一样,接触Java多年,却仍旧搞不清楚 Java 泛型中 <?>和 <? extends Object>的相似和不同.但是,这应该是一个比较高端大气上档次的Que ...
- 转载 浅谈C/C++中的static和extern关键字
浅谈C/C++中的static和extern关键字 2011-04-21 16:57 海子 博客园 字号:T | T static是C++中常用的修饰符,它被用来控制变量的存贮方式和可见性.ext ...
- 浅谈Java泛型之<? extends T>和<? super T>的区别
关于Java泛型,这里我不想总结它是什么,这个百度一下一大堆解释,各种java的书籍中也有明确的定义,只要稍微看一下就能很快清楚.从泛型的英文名字Generic type也能看出,Generic普通. ...
随机推荐
- 谈谈web上各种图片应用的优缺点
web中承载信息的主要方式就是图片与文字了,以下就是对一些web图片格式的优缺点进行归纳. 1.GIF GIF图是比较古老的web图片格式之一,可以追溯到1987,几乎所有的浏览器都支持这一种格式,老 ...
- jQuery中bind() live() delegate() on() 的区别
实例 bind(type,[data],fn) 为每个匹配元素的特定事件绑定事件处理函数 $("a").bind("click",function(){aler ...
- ES6模块化与常用功能
目前开发环境已经普及使用,如vue,react等,但浏览器环境却支持不好,所以需要开发环境编译,下面介绍下开发环境的使用和常用语法: 一,ES6模块化 1,模块化的基本语法 ES6 的模块自动采用严格 ...
- Salesforce DX 简介
Salesforce DX Salesforce DX (Salesforce Developer Experience) 是 Salesforce 推出的一个新的开发和部署模式,旨在提供更好的开发者 ...
- 四元数与欧拉角(RPY角)的相互转换
RPY角与Z-Y-X欧拉角 描述坐标系{B}相对于参考坐标系{A}的姿态有两种方式.第一种是绕固定(参考)坐标轴旋转:假设开始两个坐标系重合,先将{B}绕{A}的X轴旋转$\gamma$,然后绕{A} ...
- tkinter中combobox下拉选择控件(九)
combobox控件,下拉菜单控件 combobox控件在tkinter中的ttk下 简单的实现下: import tkinter from tkinter import ttk # 导入ttk模块, ...
- 09-OpenLDAP加密传输配置
OpenLDAP加密传输配置(CA服务器与openldap服务器异机) 阅读视图 环境准备 CA证书服务器搭建 OpenLDAP服务端与CA集成 OpenLDAP客户端配置 客户端测试验证 故障处理 ...
- android ninja【转】
Android7.0 Ninja编译原理 引言 使在Android N的系统上,初次使用了Ninja的编译系统.对于Ninja,最初的印象是用在了Chromium open source code的编 ...
- sklearn中各种分类器回归器都适用于什么样的数据呢?
作者:匿名用户链接:https://www.zhihu.com/question/52992079/answer/156294774来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请 ...
- January 10th, 2018 Week 02nd Wednesday
No need to have a reason to love you. Anything can be a reason not to love you. 喜欢你,不需要什么理由:不喜欢你,什么都 ...