转载 Java泛型:extends和super关键字

https://www.cnblogs.com/storml/p/7997845.html
(1)

因为擦除移除了类型信息,而无界的泛型参数调用的方法只等同于Object。但是我们可以限定这个泛型参数为某个类型A的子集,这样泛型参数声明的引用就可以用类型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 基本类型),编译期不会报错,但是运行期则会报错。

```java
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和List不是同一类型。

1 List<Fruit> fruitList = new ArrayList<Apple>(); // [Compile ERROR] Type mismatch: cannot convert from ArrayList<Apple> to List<Fruit>

通过使用通配符<? extends Fruit>可以解决这个问题。 Apple是Fruit的子类型,则List是 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是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]
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值