Java泛型(6):extends和super关键字
(1) <T extends A>
因为擦除移除了类型信息,而无界的泛型参数调用的方法只等同于Object。但是我们可以限定这个泛型参数为某个类型A的子集,这样泛型参数声明的引用就可以用类型A的方法了,语法为<T extends A>。下面是一个例子:
// 超能
interface SuperPower { }
// 千里眼
interface SuperVision extends SuperPower { void see(); }
// 顺风耳
interface SuperHearing extends SuperPower { void hear(); } // 超级英雄
class SuperHero<P extends SuperPower> {
P power;
SuperHero(P power) { this.power = power; }
P getPower() { return power; }
} // 只会千里眼的英雄
class SuperVisionMan<P extends SuperVision> extends SuperHero<P> {
SuperVisionMan(P power) { super(power); }
void see() { power.see(); }
} // 只会顺风耳的英雄
class SuperHearingMan<P extends SuperHearing> extends SuperHero<P> {
SuperHearingMan(P power) { super(power); }
void hear() { power.hear(); }
} // 都会的的英雄
class SuperAllSkillsMan<P extends SuperVision & SuperHearing> extends SuperHero<P> {
SuperAllSkillsMan(P power) { super(power); }
void see() { power.see(); }
void hear() { power.hear(); }
} class SampleSuperVision implements SuperVision{
@Override
public void see() { System.out.println("I can see anything!"); }
} class SampleSuperHearing implements SuperHearing{
@Override
public void hear() { System.out.println("I can hear anything!"); }
} class SampleSuperAllSkills implements SuperVision, SuperHearing{
@Override
public void see() { System.out.println("I'm good at all skills and i can see anything!"); }
@Override
public void hear() { System.out.println("I'm good at all skills and i can hear anything!"); }
} public class EpicBattle {
public static void main(String[] args) {
SuperVisionMan<SuperVision> man1 = new SuperVisionMan<>(new SampleSuperVision());
man1.see(); // I can see anything!
SuperHearingMan<SuperHearing> man2 = new SuperHearingMan<>(new SampleSuperHearing());
man2.hear(); // I can hear anything!
SuperAllSkillsMan<SampleSuperAllSkills> man3 = new SuperAllSkillsMan<>(new SampleSuperAllSkills());
man3.see(); // I'm good at all skills and i can see anything!
man3.hear(); // I'm good at all skills and i can hear anything!
}
}
(2) <? extends T> / <? super T>
前置条件:
class Fruit {
private String name;
public Fruit(String name) { this.name = name; }
public String getName() { return name; }
@Override public String toString() { return name; }
}
class Apple extends Fruit {
public Apple(String name) { super(name); }
}
class RedFushi extends Apple {
public RedFushi(String name) { super(name); }
}
class Orange extends Fruit {
public Orange(String name) { super(name); }
}
我们先研究一种特殊的数组行为:可以向导出类的数组赋予基本类型的数组引用。这种行为是可以的。但是,如果往这个导出类的数组中插入其他类型的值(extends 基本类型),编译期不会报错,但是运行期则会报错。
Fruit[] fruits = new Apple[10];
fruits[0] = new Apple("Apple1");
fruits[1] = new Orange("Orange1"); // Compile OK; Run java.lang.ArrayStoreException.
如果替换成List容器,则编译期就会报错。原因与向上转型(Apple->Fruit)无关,根本原因在于ArrayList<Apple>和List<Fruit>不是同一类型。
List<Fruit> fruitList = new ArrayList<Apple>(); // [Compile ERROR] Type mismatch: cannot convert from ArrayList<Apple> to List<Fruit>
通过使用通配符<? extends Fruit>可以解决这个问题。 Apple是Fruit的子类型,则List<Apple>是 List<? extends Fruit>的子类型。但是一旦执行了这种向上转型,就失去了向其中传入任何对象的能力。你不能够往一个使用了<? extends T>的数据结构里写入任何的值。原因非常的简单,你可以这样想:这个<? extends Fruit>通配符告诉编译器我们在处理一个类型Fruit的子类型,但我们不知道这个子类型究竟是什么。因为没法确定,为了保证类型安全,我们就不允许往里面加入任何这种类型的数据。但是可以get到Fruit类型的元素对象。
List<? extends Fruit> fruitList1 = new ArrayList<Apple>();
fruitList1.add(new Apple("Apple1")); // [Compile ERROR] The method add(capture#1-of ? extends Fruit) in the type List<capture#1-of ? extends Fruit> is not applicable for the arguments (Apple)
fruitList1.add(new Object()); // [Compile ERROR] The method add(capture#2-of ?extends Fruit) in the type List<capture#2-of ? extends Fruit> is not applicable for the arguments (Object)
可以使用下面的方法代替:
List<Apple> tmpList1 = new ArrayList<>(Arrays.asList(new Apple("Apple1"), new Apple("Apple2")));
fruitList1 = tmpList1;
System.out.println(fruitList1.get(0) + "/" + fruitList1.get(1)); // Apple1/Apple2
List<Orange> tmpList2 = new ArrayList<Orange>(Arrays.asList(new Orange("Orange1"), new Orange("Orange2")));
fruitList1 = tmpList2;
System.out.println(fruitList1.get(0) + "/" + fruitList1.get(1)); // Orange1/Orange2
也可以通过逆变使用超类型通配符,Fruit是Apple的父类型,则List<Fruit>是List<? super Apple>的子类型。
List<? super Apple> fruitList2 = new ArrayList<Fruit>();
fruitList2.add(new Apple("Apple1"));
fruitList2.add(new RedFushi("RedFushi1"));
fruitList2.add(new Orange("Orange1")); // [Compile ERROR] The method add(capture#9-of ? super Apple) in the type List<capture#9-of ? super Apple> is not applicable for the arguments (Orange)
fruitList2.add(new Fruit("Fruit1")); // [Compile ERROR] The method add(capture#9-of ? super Apple) in the type List<capture#9-of ? super Apple> is not applicable for the arguments (Fruit)
(3) PECS法则
总结<? extends T>/<? super T>通配符的特征,我们可以得出以下结论:
1. 如果你想在方法中从input参数里获取数据,使用<? extends T>通配符
2. 如果你想在方法中把对象写入一个input参数中,使用<? super T>通配符
3. 如果你既想存,又想取,那就别用通配符
PECS指“Producer Extends,Consumer Super”。换句话说,如果方法中的参数表示一个生产者,就使用<? extends T>;如果它表示一个消费者,就使用<? super T>。
import java.util.Collection;
import java.util.Stack; public class MyStack<E> extends Stack<E> {
private static final long serialVersionUID = 1L;
// 如果你想在方法中从input参数里获取数据,使用<? extends T>通配符
public void pushAll(Collection<? extends E> params) {
for(E t : params) { push(t); }
}
// 如果你想在方法中把对象写入一个input参数中,使用<? super T>通配符
public void popAll(Collection<? super E> results) {
while(!empty()) { results.add(pop()); }
}
}
MyStack<Fruit> stack1 = new MyStack<Fruit>();
Collection<Apple> appleList = new ArrayList<>(Arrays.asList(new Apple("Apple1"), new Apple("Apple2")));
Collection<Orange> orangeList = new ArrayList<>(Arrays.asList(new Orange("Orange1"), new Orange("Orange2")));
Collection<Fruit> fruitList = new ArrayList<>(Arrays.asList(new Apple("Apple3"), new RedFushi("RedFushi3"), new Orange("Orange3")));
stack1.pushAll(appleList);
stack1.pushAll(orangeList);
stack1.pushAll(fruitList);
Collection<Fruit> resultList = new ArrayList<>();
stack1.popAll(resultList);
for (Fruit res : resultList) {
System.out.print("[" + res.getName() + "--" + res.getClass().getSimpleName() + "] ");
// [Orange3--Orange] [RedFushi3--RedFushi] [Apple3--Apple] [Orange2--Orange] [Orange1--Orange] [Apple2--Apple] [Apple1--Apple]
}
Java泛型(6):extends和super关键字的更多相关文章
- java泛型中extends 和 super的区别
一般对泛型中extends 和 super 的区别是这样介绍的: 关键字说明 ? 通配符类型 <? extends T> 表示类型的上界,表示参数化类型的可能是T 或是 T的子类 < ...
- Java泛型中extends和super的理解(转)
E – Element (在集合中使用,因为集合中存放的是元素) T – Type(Java 类) K – Key(键) V – Value(值) N – Number(数值类型) ? – 表示不确定 ...
- Java泛型中extends和super的理解
作者:zhang siege链接:https://www.zhihu.com/question/20400700/answer/91106397来源:知乎著作权归作者所有.商业转载请联系作者获得授权, ...
- Java泛型中extends和super的区别?
<? extends T>和<? super T>是Java泛型中的"通配符(Wildcards)"和"边界(Bounds)"的概念. ...
- JAVA继承时this和super关键字
JAVA继承时this和super关键字 本文主要讨论在方法前使用this或super关键字时,编译器在什么地方查找对应的函数. 在子类中指定this关键字.首先在本类中查找,如果本类中找不到,再在父 ...
- 浅谈Java泛型中的extends和super关键字(转)
通配符 在本文的前面的部分里已经说过了泛型类型的子类型的不相关性.但有些时候,我们希望能够像使用普通类型那样使用泛型类型: 向上造型一个泛型对象的引用 向下造型一个泛型对象的引用 向上造型一个泛型对象 ...
- JAVA 泛型 通配符? extends super限定,实例区分extends super限定的作用用法
java泛型中的关键字 ? 表示通配符类型 <? extends T> 既然是extends,就是表示泛型参数类型的上界,说明参数的类型应该是T或者T的子类. <? super T& ...
- 浅谈Java泛型之<? extends T>和<? super T>的区别
关于Java泛型,这里我不想总结它是什么,这个百度一下一大堆解释,各种java的书籍中也有明确的定义,只要稍微看一下就能很快清楚.从泛型的英文名字Generic type也能看出,Generic普通. ...
- JAVA泛型知识--> <? extends T>和<? super T>
<? extends T> 和 <? super T> 是Java泛型中的“通配符(Wildcards)” 和 “边界(Bounds)”的概念 <? extends T& ...
随机推荐
- okhttp初识拦截器
拦截器流程: 简单回顾同步 / 异步: 同步请求就是执行请求的操作是阻塞式,直到HTTP响应返回. 异步请求就类似于非阻塞式的请求,它的执行结果一般都是通过接口回调的方式告知调用者. okHttp拦截 ...
- linux内核无锁缓冲队列kfifo原理
Linux kernel里面从来就不缺少简洁,优雅和高效的代码 比如,通过限定写入的数据不能溢出和内存屏障实现在单线程写单线程读的情况下不使用锁.因为锁是使用在共享资源可能存在冲突的情况下.还用设置b ...
- python __file__ is not defined 解决方法
python __file__ is not defined 解决方法 __file__ 是在python module 被导入的时候生成的一个变量,所以在 __file__ 不能被使用,但是又想获取 ...
- python_推导式
列表推导式 目的:方便的生成一个列表 格式: v1 = [i for i in 可迭代对象 ] v2 = [i for i in 可迭代对象 if条件]#条件为True菜进行append v1 = [ ...
- 端口与服务-ftp服务
端口与服务-ftp服务 1概述 1.1.从先知和乌云上爬取端口历史漏洞报告,总结报告 1.2.全面总结,出具一个表格之类的汇总表 2.ftp # -*- coding: utf-8 -*- impor ...
- avcodec_decode_video2函数
转自 https://www.xuebuyuan.com/2156374.html 该函数的作用是实现压缩视频的解码.在avcodec.h中的声明方式如下: int avcodec_decode_vi ...
- mybatis注解大全
注解 目标 相对应的 XML 描述 @CacheNamespace 类 <cache> 为给定的命名空间 (比如类) 配置缓存. 属性:implemetation,eviction, fl ...
- 清除文本中Html的标签
/// <summary> /// 清除文本中Html的标签 /// </summary> /// <param name="Content"> ...
- Win7 : 'java' is not recognized as internal or external command,
Java application is not working in Win 7 64-bit http://answers.microsoft.com/en-us/windows/forum/win ...
- 新西达电调初始化代码,使用nodejs ffi技术调用wiringpi,代码使用typescript编写
这是我设计的F450四轴飞行器飞控代码的一部分 运行在orangepi-zero上,操作系统是armbian,思路是使用node-ffi调用wiringpi的so库与GPIO通信,然后控制端逻辑代码使 ...