Java泛型中的 “?super T“ 与 “?extends T“ 有何不同

经常发现有List<? super T>Set<? extends T>的声明,是什么意思呢?<? super T>表示包括T在内的任何T的父类,<? extends T>表示包括T在内的任何T的子类,下面我们详细分析一下两种通配符具体的区别.

PECS

首先,Java的泛型在编译期会做类型擦除,这样会引起安全问题.为了安全,所有与泛型相关的异常都应该在编译期间发现,因此为了泛型的绝对安全,java在设计时做了相关的限制:

List<? extends T>表示该list集合中存放的都是T的子类型(包括T自身),由于T的子类型可能有很多,但是我们存放元素时实际上只能存放其中的一种子类型(这是为了泛型安全,因为其会在编译期间生成 桥接方法(Bridge Methods),该方法中会出现强制转换,若出现多种子类型,则会强制转换失败),例子如下:

List<? extends Number> list=new ArrayList<Number>();

list.add(4.0);//编译错误
list.add(3);//编译错误

上例中添加的元素类型不止一种,这样编译器生成的桥接方法中的强制转换会失败,为了安全,Java只能将其设计成不能添加元素。

虽然List<? extends T>不能添加元素,但是由于其中的元素都有一个共性–有共同的父类,因此我们在获取元素时可以将他们统一强制转换为T类型,我们称之为get原则。

对于*List<? super T>*其list中存放的都是T的父类型元素(包括T),我们在向其添加元素时,只能向其添加T的子类型元素(包括T类型),这样在编译期间将其强制转换为T类型时是类型安全的,因此可以添加元素,例子如下:

List<? super Number> list=new ArrayList<Number>();

list.add(2.0);
list.add(3.0);

但是,由于该集合中的元素都是T的父类型(包括T),其中的元素类型众多,在获取元素时我们无法判断是哪一种类型,故设计成不能获取元素,我们称之为put原则。

实际上,我们采用extendssuper来扩展泛型的目的是为了弥补例如List<T>只能存放一种特定类型数据的不足.

请 记住PECS原则:生产者(Producer)使用extends,消费者(Consumer)使用super。

  • 生产者(出) 使用extends

如 果你需要一个列表提供T类型的元素(即你想从列表中get出T类型的元素),你需要把这个列表声明成<? extends T>,比如List<? extends Integer>,因此你不能往该列表中添加任何元素。

  • 消费者(入) 使用super

如 果需要一个列表使用T类型的元素(即你想把T类型的元素put到列表中),你需要把这个列表声明成<? super T>,比如List<? super Integer>,因此你不能保证从中读取到的元素的类型。

  • 即 是生产者,也是消费者

如 果一个列表即要生产,又要消费,你不能使用泛型通配符声明列表,比如List<Integer>

参考java.util.Collections里的copy方法:

public static <T> void copy(List<? super T> dest, List<? extends T> src)

要从src里get所以是extends,要向dest里写入所以是super.

桥接方法研究

一个使用Consumer <T>的例子:

package wjw.test.java8.generic;

import java.util.function.Consumer;

public class TestGeneric {
  public static void main(String[] args) {
    Consumer<String> c = new Consumer<String>() {   // <1>
      @Override
      public void accept(String s) {
        System.out.println(s);
      }
    };
    c.accept("hello lambda");
  }
}

<1>: 创建了一个具体类型是String的Consumer

编译生成的字节码:

Compiled from "TestGeneric.java"
public class wjw.test.java8.generic.TestGeneric {
  public wjw.test.java8.generic.TestGeneric();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #16                 // class wjw/test/java8/generic/TestGeneric$1
       3: dup
       4: invokespecial #18                 // Method wjw/test/java8/generic/TestGeneric$1."<init>":()V
       7: astore_1
       8: aload_1
       9: ldc           #19                 // String hello lambda
      11: invokeinterface #21,  2           // InterfaceMethod java/util/function/Consumer.accept:(Ljava/lang/Object;)V
      16: return
}

由于编译时进行了类型擦除,第17行的Consumer.accept的方法里传的参数是Object.

生成的匿名类:

import java.util.function.*;

class LambdaOld$1 implements Consumer<String> {
    LambdaOld$1() {
        super();
    }
    
    @Override
    public void accept(final String s) {
        System.out.println(s);
    }
    
    @Override
    public /* bridge */ void accept(final Object o) {
        this.accept((String)o);
    }
}

在桥接方法accept里,第15行会进行强制类型转换然后调用第9行的接受具体类型参数的accept方法.

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱游泳的老白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值