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& ...
随机推荐
- percona-toolkit主从同步整理(MySQL)
前言:MYSQL主从同步架构是目前使用最多的数据库架构之一,尤其是负载比较大的网站,因此对于主从同步的管理也就显得非常重要.而数据作为软件的核心部分,对于其有效的管理显得更为重要.随着时间的推移,软件 ...
- C#中使用ListView动态添加数据不闪烁(网上方法会出问题)
最近需要使用做一个动态行显示,所以就用到了ListView控件,在网上也查到了关于动态添加不闪烁的方式都是如下: 首先,自定义一个类ListViewNF,继承自 System.Windows.Form ...
- 使用WebSocket实现简单的在线聊天室
前言:我自已在网上找好了好多 WebSocket 制作 在线聊天室的案列,发现大佬们写得太高深了 我这种新手看不懂,所以就自已尝试写了一个在线简易聊天室 (我只用了js 可以用jq ) 话不多说,直接 ...
- c++中lambda表达式的用法
#include <iostream> using namespace std; int main(){ ; auto func1 = [=](;}; auto func2 = [& ...
- nginx静态资源配置(转发)
传统的web项目,一般都将静态资源存放在 webroot的目录下,这样做很方便获取静态资源,但是如果说web项目很大,用户很多,静态资源也很多时,服务器的性能 或许就会很低下了.这种情况下一般都会需要 ...
- luogu 2294 [HNOI2005]狡猾的商人 差分约束
一个差分约束模型,只需判一下有没有负环即可. #include <bits/stdc++.h> #define N 103 #define M 2004 #define setIO(s) ...
- 小米 oj 马走日 (bfs 或 双向bfs)
马走日 序号:#56难度:困难时间限制:1500ms内存限制:10M 描述 在中国象棋中,马只能走日字型.现在给出一个由 N*M 个格子组成的中国象棋棋盘( 有(N+1)*(M+1)个交叉点可以落子 ...
- 51 Nod 1287 加农炮(单调队列思想+二分)
1287 加农炮 题目来源: Codility 基准时间限制:1 秒 空间限制:131072 KB 分值: 40 难度:4级算法题 收藏 关注 一个长度为M的正整数数组A,表示从左向右的地形高度 ...
- linux上安装nginx详细步骤
一.安装依赖包 yum install gcc gcc-c++ pcre-devel patch libffi-devel python-devel zlib-devel bzip2-devel op ...
- C# Unicode编码解码
public static class CommpnHelpEx { /// <summary> /// unicode编码 /// </summary> /// <pa ...