• Java泛型(6):extends和super关键字


    (1) <T extends A>

    因为擦除移除了类型信息,而无界的泛型参数调用的方法只等同于Object。但是我们可以限定这个泛型参数为某个类型A的子集,这样泛型参数声明的引用就可以用类型A的方法了,语法为<T extends A>。下面是一个例子:

     1 // 超能
     2 interface SuperPower { }
     3 // 千里眼
     4 interface SuperVision extends SuperPower { void see(); }
     5 // 顺风耳
     6 interface SuperHearing extends SuperPower { void hear(); }
     7 
     8 // 超级英雄
     9 class SuperHero<P extends SuperPower> {
    10     P power;
    11     SuperHero(P power) { this.power = power; }
    12     P getPower() { return power; }
    13 }
    14 
    15 // 只会千里眼的英雄
    16 class SuperVisionMan<P extends SuperVision> extends SuperHero<P> {
    17     SuperVisionMan(P power) { super(power); }
    18     void see() { power.see(); }
    19 }
    20 
    21 // 只会顺风耳的英雄
    22 class SuperHearingMan<P extends SuperHearing> extends SuperHero<P> {
    23     SuperHearingMan(P power) { super(power); }
    24     void hear() { power.hear(); }
    25 }
    26 
    27 // 都会的的英雄
    28 class SuperAllSkillsMan<P extends SuperVision & SuperHearing> extends SuperHero<P> {
    29     SuperAllSkillsMan(P power) { super(power); }
    30     void see() { power.see(); }
    31     void hear() { power.hear(); }
    32 }
    33 
    34 class SampleSuperVision implements SuperVision{
    35     @Override
    36     public void see() { System.out.println("I can see anything!"); }
    37 }
    38 
    39 class SampleSuperHearing implements SuperHearing{
    40     @Override
    41     public void hear() { System.out.println("I can hear anything!"); }
    42 }
    43 
    44 class SampleSuperAllSkills implements SuperVision, SuperHearing{
    45     @Override
    46     public void see() { System.out.println("I'm good at all skills and i can see anything!"); }
    47     @Override
    48     public void hear() { System.out.println("I'm good at all skills and i can hear anything!"); }
    49 }
    50 
    51 public class EpicBattle {
    52     public static void main(String[] args) {
    53         SuperVisionMan<SuperVision> man1 = new SuperVisionMan<>(new SampleSuperVision());
    54         man1.see(); // I can see anything!
    55         SuperHearingMan<SuperHearing> man2 = new SuperHearingMan<>(new SampleSuperHearing());
    56         man2.hear(); // I can hear anything!
    57         SuperAllSkillsMan<SampleSuperAllSkills> man3 = new SuperAllSkillsMan<>(new SampleSuperAllSkills());
    58         man3.see(); // I'm good at all skills and i can see anything!
    59         man3.hear(); // I'm good at all skills and i can hear anything!
    60     }
    61 }

    (2) <? extends T> / <? super T>

    前置条件:

     1 class Fruit {
     2     private String name;
     3     public Fruit(String name) { this.name = name; }
     4     public String getName() { return name; }
     5     @Override public String toString() { return name; }
     6 }
     7 class Apple extends Fruit {
     8     public Apple(String name) { super(name); }
     9 }
    10 
    11 class RedFushi extends Apple {
    12     public RedFushi(String name) { super(name); }
    13 }
    14 
    15 class Orange extends Fruit {
    16     public Orange(String name) { super(name); }
    17 }

    我们先研究一种特殊的数组行为:可以向导出类的数组赋予基本类型的数组引用。这种行为是可以的。但是,如果往这个导出类的数组中插入其他类型的值(extends 基本类型),编译期不会报错,但是运行期则会报错。

    1 Fruit[] fruits = new Apple[10];
    2 fruits[0] = new Apple("Apple1");
    3 fruits[1] = new Orange("Orange1"); // Compile OK; Run java.lang.ArrayStoreException.

    如果替换成List容器,则编译期就会报错。原因与向上转型(Apple->Fruit)无关,根本原因在于ArrayList<Apple>和List<Fruit>不是同一类型。

    1 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类型的元素对象。

    1 List<? extends Fruit> fruitList1 = new ArrayList<Apple>();
    2 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)
    3 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)

    可以使用下面的方法代替:

    1 List<Apple> tmpList1 = new ArrayList<>(Arrays.asList(new Apple("Apple1"), new Apple("Apple2")));
    2 fruitList1 = tmpList1;
    3 System.out.println(fruitList1.get(0) + "/" + fruitList1.get(1)); // Apple1/Apple2
    4 List<Orange> tmpList2 = new ArrayList<Orange>(Arrays.asList(new Orange("Orange1"), new Orange("Orange2")));
    5 fruitList1 = tmpList2;
    6 System.out.println(fruitList1.get(0) + "/" + fruitList1.get(1)); // Orange1/Orange2

    也可以通过逆变使用超类型通配符,Fruit是Apple的父类型,则List<Fruit>是List<? super Apple>的子类型。

    1 List<? super Apple> fruitList2 = new ArrayList<Fruit>();
    2 fruitList2.add(new Apple("Apple1"));
    3 fruitList2.add(new RedFushi("RedFushi1"));
    4 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)
    5 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>。

     1 import java.util.Collection;
     2 import java.util.Stack;
     3 
     4 public class MyStack<E> extends Stack<E> {
     5     private static final long serialVersionUID = 1L;
     6     // 如果你想在方法中从input参数里获取数据,使用<? extends T>通配符
     7     public void pushAll(Collection<? extends E> params) {
     8         for(E t : params) { push(t); }
     9     }
    10     // 如果你想在方法中把对象写入一个input参数中,使用<? super T>通配符
    11     public void popAll(Collection<? super E> results) {
    12         while(!empty()) { results.add(pop()); }
    13     }
    14 }
     1 MyStack<Fruit> stack1 = new MyStack<Fruit>();
     2 Collection<Apple> appleList = new ArrayList<>(Arrays.asList(new Apple("Apple1"), new Apple("Apple2")));
     3 Collection<Orange> orangeList = new ArrayList<>(Arrays.asList(new Orange("Orange1"), new Orange("Orange2")));
     4 Collection<Fruit> fruitList = new ArrayList<>(Arrays.asList(new Apple("Apple3"), new RedFushi("RedFushi3"), new Orange("Orange3")));
     5 stack1.pushAll(appleList);
     6 stack1.pushAll(orangeList);
     7 stack1.pushAll(fruitList);
     8 Collection<Fruit> resultList = new ArrayList<>();
     9 stack1.popAll(resultList);
    10 for (Fruit res : resultList) {
    11     System.out.print("[" + res.getName() + "--" + res.getClass().getSimpleName() + "] ");
    12     // [Orange3--Orange] [RedFushi3--RedFushi] [Apple3--Apple] [Orange2--Orange] [Orange1--Orange] [Apple2--Apple] [Apple1--Apple] 
    13 }
  • 相关阅读:
    Mysql 主主复制失败恢复【转】
    linux的curl用法【转】
    做Mysql主从时,注意使用replicate_wild_do_table和replicate-wild-ignore-table【转】
    MyEclipse 2014配置Maven
    使用MyEclipse 2014构建Maven项目的两种方法
    Struts2的输入校验
    Java Web之Filter
    Struts2国际化
    Struts2实现登录流程
    Java实现文件MD5加密
  • 原文地址:https://www.cnblogs.com/storml/p/7997845.html
Copyright © 2020-2023  润新知