当前位置: 首页 > news >正文

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方法.

相关文章:

  • MaterialDesign组件
  • 如何在不修改原始数组的情况下反转数组?
  • 以太坊学习二:签名
  • 配置本地Maven仓库——IDEA配置本地Maven源
  • mysql8-索引的使用规则验证
  • Python神经网络入门与实战,神经网络算法python实现
  • un8.31:用jQuery实现调用不同项目api接口的功能。
  • Java Agent入门教程
  • 航班信息查询 易语言代码
  • C++ 小游戏 视频及资料集(四)
  • 利用HFSS-API设计指数渐变传输线
  • js的es6
  • 开源交流丨任务or实例 详解大数据DAG调度系统Taier任务调度
  • 配置Tomcat时系统环境变量已经配置好,但是启动Tomcat时还是闪退的解决办法
  • app,小程序打包
  • JavaScript 如何正确处理 Unicode 编码问题!
  • “Material Design”设计规范在 ComponentOne For WinForm 的全新尝试!
  • 【技术性】Search知识
  • Android开发 - 掌握ConstraintLayout(四)创建基本约束
  • javascript从右向左截取指定位数字符的3种方法
  • Linux各目录及每个目录的详细介绍
  • Linux下的乱码问题
  • XML已死 ?
  • 笨办法学C 练习34:动态数组
  • 分类模型——Logistics Regression
  • 构建二叉树进行数值数组的去重及优化
  • 免费小说阅读小程序
  • 巧用 TypeScript (一)
  • 问题之ssh中Host key verification failed的解决
  • 验证码识别技术——15分钟带你突破各种复杂不定长验证码
  • CMake 入门1/5:基于阿里云 ECS搭建体验环境
  • Play Store发现SimBad恶意软件,1.5亿Android用户成受害者 ...
  • ​Base64转换成图片,android studio build乱码,找不到okio.ByteString接腾讯人脸识别
  • !$boo在php中什么意思,php前戏
  • $forceUpdate()函数
  • (52)只出现一次的数字III
  • (Redis使用系列) Springboot 在redis中使用BloomFilter布隆过滤器机制 六
  • (附源码)基于ssm的模具配件账单管理系统 毕业设计 081848
  • (附源码)计算机毕业设计SSM智能化管理的仓库管理
  • (没学懂,待填坑)【动态规划】数位动态规划
  • (区间dp) (经典例题) 石子合并
  • (十)c52学习之旅-定时器实验
  • (四)Android布局类型(线性布局LinearLayout)
  • (四)JPA - JQPL 实现增删改查
  • (算法)Game
  • (转) RFS+AutoItLibrary测试web对话框
  • (转)Linq学习笔记
  • (转)程序员技术练级攻略
  • .MSSQLSERVER 导入导出 命令集--堪称经典,值得借鉴!
  • .NET 回调、接口回调、 委托
  • .net经典笔试题
  • 。Net下Windows服务程序开发疑惑
  • /etc/fstab 只读无法修改的解决办法
  • @TableId注解详细介绍 mybaits 实体类主键注解
  • [@Controller]4 详解@ModelAttribute