(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. MySQL No Install zip安装方法

    解压 解压安装包到一个目录,比如:D:\Database\mysql-5.1.55 配置 创建配置文件 创建一个my.ini文件,MYSQL目录下有几个my开头的ini文件,是针对不同配置使用,具体可 ...

  2. SQL 递归查询,意淫CTE递归的执行步骤

    今天用到了sql的递归查询.递归查询是CTE语句with xx as(....)实现的. 假如表Category数据如下. 我们想查找机枪这个子分类极其层次关系(通过子节点,查询所有层级节点).以下是 ...

  3. config.json读取和存储

    json格式的配置文件的读取和存储 public class ConfigHelper { public static T GetConfig<T>(string path) { if ( ...

  4. 软件测试常用的linux命令

    不同Linux发行版的命令数量不一样,但Linux发行版本最少的命令也有200多个.这里我把比较重要和使用频率最多的命令,按照它们在系统中的作用分成下面六个部分一一介绍. ◆ 安装和登录命令:logi ...

  5. 深入了解java线程池(转载)

    出处:http://www.cnblogs.com/dolphin0520/ 本文归作者海子和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责 ...

  6. jvm虚拟机笔记<六> 运行期优化

    这节我们总结一下JVM运行期的优化问题. https://www.cnblogs.com/zhouyuqin/p/5224573.html JVM运行期优化 即时编译器(JIT) 编译对象与触发条件 ...

  7. BZOJ 2982 combination 脑子+组合数学

    可以发现,整个数列构成一个树形结构,并且是个完全二叉堆(小根堆). 并且这个堆的形态在给定$n$后是固定的,第1个位置上显然只能放1. 对子树的根来说,他自己是所分得的数集中最小的那个,所以从剩下$s ...

  8. detach([expr]) 从DOM中删除所有匹配的元素。

    detach([expr]) 概述 从DOM中删除所有匹配的元素.大理石构件 这个方法不会把匹配的元素从jQuery对象中删除,因而可以在将来再使用这些匹配的元素.与remove()不同的是,所有绑定 ...

  9. @JsonIgnore等

    作用:在json序列化时将java bean中的一些属性忽略掉,序列化和反序列化都受影响. 使用方法:一般标记在属性或者方法上,返回的json数据即不包含该属性. 场景模拟: 需要把一个List< ...

  10. codeforces#1187E. Tree Painting(树换根)

    题目链接: http://codeforces.com/contest/1187/problem/E 题意: 给出一颗树,找到一个根节点,使所有节点的子节点数之和最大 数据范围: $2 \le n \ ...