(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关键字的更多相关文章

  1. java泛型中extends 和 super的区别

    一般对泛型中extends 和 super 的区别是这样介绍的: 关键字说明 ? 通配符类型 <? extends T> 表示类型的上界,表示参数化类型的可能是T 或是 T的子类 < ...

  2. Java泛型中extends和super的理解(转)

    E – Element (在集合中使用,因为集合中存放的是元素) T – Type(Java 类) K – Key(键) V – Value(值) N – Number(数值类型) ? – 表示不确定 ...

  3. Java泛型中extends和super的理解

    作者:zhang siege链接:https://www.zhihu.com/question/20400700/answer/91106397来源:知乎著作权归作者所有.商业转载请联系作者获得授权, ...

  4. Java泛型中extends和super的区别?

    <? extends T>和<? super T>是Java泛型中的"通配符(Wildcards)"和"边界(Bounds)"的概念. ...

  5. JAVA继承时this和super关键字

    JAVA继承时this和super关键字 本文主要讨论在方法前使用this或super关键字时,编译器在什么地方查找对应的函数. 在子类中指定this关键字.首先在本类中查找,如果本类中找不到,再在父 ...

  6. 浅谈Java泛型中的extends和super关键字(转)

    通配符 在本文的前面的部分里已经说过了泛型类型的子类型的不相关性.但有些时候,我们希望能够像使用普通类型那样使用泛型类型: 向上造型一个泛型对象的引用 向下造型一个泛型对象的引用 向上造型一个泛型对象 ...

  7. JAVA 泛型 通配符? extends super限定,实例区分extends super限定的作用用法

    java泛型中的关键字 ? 表示通配符类型 <? extends T> 既然是extends,就是表示泛型参数类型的上界,说明参数的类型应该是T或者T的子类. <? super T& ...

  8. 浅谈Java泛型之<? extends T>和<? super T>的区别

    关于Java泛型,这里我不想总结它是什么,这个百度一下一大堆解释,各种java的书籍中也有明确的定义,只要稍微看一下就能很快清楚.从泛型的英文名字Generic type也能看出,Generic普通. ...

  9. JAVA泛型知识--> <? extends T>和<? super T>

    <? extends T> 和 <? super T> 是Java泛型中的“通配符(Wildcards)” 和 “边界(Bounds)”的概念 <? extends T& ...

随机推荐

  1. C#实现异步阻塞TCP(Send,Receive,Accept,Connect)

    1.类 (1)服务器端操作类 public class TcpServiceSocket { //接收数据事件 public Action<Socket, string> recvMess ...

  2. ACM-ICPC 2017 沈阳赛区现场赛 G. Infinite Fraction Path && HDU 6223(BFS)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6223 参考题解:https://blog.csdn.net/qq_40482495/article/d ...

  3. [RxJS] RxJS Advanced Patterns Operate Heavily Dynamic UIs

    Check the playground. import {Counter, CountDownState, ConterStateKeys, PartialCountDownState} from ...

  4. C++Error2208:...尝试引用已删除的函数

    C++Error2208:…尝试引用已删除的函数在使用自己编写的类实例化c++stl容器vector时,调用了vector.erase()方法,结果编译器报了以下错误 错误 C2280 “(类名):: ...

  5. Linux 防火墙遇到的问题

    一直报这个错误,写个博客来记录一下问题 在centos7中执行service iptables start 报如下错误 执行如下命令 systemctl stop firewalld systemct ...

  6. Educational Codeforces Round 34 (Rated for Div. 2) B题【打怪模拟】

    B. The Modcrab Vova is again playing some computer game, now an RPG. In the game Vova's character re ...

  7. QueryList之flatten方法

    正确用法: $data = $ql->get($url)->query()->getData();$data = $data->flatten()->all(); 注意: ...

  8. Eclipse一直building workspace问题解决

    今天新导入了一个maven项目,但是总是一直building workspace 解决方案: 去掉图中Maven  Project Builder勾选

  9. 【集训队作业2018】count

    CSP后第一发Blog. 这道题没有一下子过掉,开始还推出了错的结论.在错误的生成函数里绕了好久-- 很显然的转笛卡尔树,一个笛卡尔树对应一种序列.只要考虑一个笛卡尔树是否合法. 贪心地填数发现,从根 ...

  10. 点击事件解绑unbind

    $(".choose").unbind("click").click(function(){} 这个类先解绑了点击事件再添加个点击事件有事如果不这样你点击第二次 ...