【再探】Java—泛型

 Java 泛型本质是参数化类型,可以用在类、接口和方法的创建中。

1 “擦除式”泛型

Java的“擦除式”的泛型实现一直受到开发者的诟病。

“擦除式”的实现几乎只需要在Javac编译器上做出改进即可,不要改动字节码、虚拟机,也保证了以前没有使用泛型的库可以之间运行在java 5.0 之上。但是这个带来了以下弊端:

  1. 例如对于List<String> 和 List<Integer> ,在运行时由于擦除了,所以这两个都变成了List,因此它们在运行中是同一类型。(而对于C#的泛型来说,无论在源码中、编译后及运行时它们始终是不同的类型)。这导致在运行时,获取不到类型信息。
  2. “擦除式”的实现,是在元素被赋值时编译器自动插入类型检查指令,访问元素时,自动插入类型强制转换指令。这样频繁的类型检查及转换,导致Java的泛型性能差于C#的泛型。
public class EraseGeneric {

    private static class Holder<T> {
        T t;

        public T getT() {
            return t;
        }

        public void setT(T t) {
            this.t = t;
        }
    }

    public static void main(String[] args) {
        Holder<String> holder = new Holder<>();
        holder.setT("hello");
        String str = holder.getT();
    }

}

图 Holder类被编译后的字节码片段

图 main 方法中,对于Holder类型的赋值及访问操作字节码

1.1 擦除的补偿

如果要在运行时获取类型信息,那么可以通过引入类型标签来对擦除进行补偿。

public class TypeTagGeneric {

    private static class User {
        public User() {
        }
    }

    private static <T> void fun(Class<T> kind) throws InstantiationException, IllegalAccessException {
        T t = kind.newInstance();
        System.out.println(t);
    }

    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        fun(User.class);
        int[] array1 = new int[10];
        String[] array2 = new String[10];
    }

}

2 协变与逆变

A 类型是B的父类型,对于某个构造器,构造出的复杂类型A`与B`。

协变

A`仍然是B`的父类型。比如Java中的数组,A[] 仍是B[]的父类型。

逆变

B`是A`的父类型。

抗变

A`与B`没有任何继承关系。例如List<A> 与List<B>没有任何继承关系。

表 协变、逆变与抗变

2.1 数组与泛型

T[] t = new T[10]; 这个代码是错误的,Java中规定不能创建泛型数组。

因为Java 在运行时,无法获取泛型的类型信息,因为在创建数组时,也就无法获取到泛型参数所表示的确切类型。

2.1.1 数组的类型

Java中数组的种类有两种:

  1. 基础类型的数组:[ + 开头大写字母。

int[] : [I

  1. 引用类型的数组:[ + L + 类型。

String[] array2 : [Ljava/lang/String

public class ArrayGeneric {

    private static class Fruit {}

    private static class Apple extends Fruit {}

    public static void main(String[] args) {
        Fruit[] fruits = new Fruit[10];
        Apple[] apples = new Apple[10];
        System.out.println(fruits instanceof Fruit[]); // true
        System.out.println(fruits instanceof Apple[]); // false
        System.out.println(apples instanceof Fruit[]); // true
        System.out.println(apples instanceof Apple[]); // true
        fruits = apples;
//        apples = fruits; // 编译错误
        System.out.println(fruits.getClass().getSuperclass()); // class java.lang.Object
        System.out.println(apples.getClass().getSuperclass()); // class java.lang.Object
//        getSuperclass() 方法:如果此 Class 表示 Object 类、一个接口、一个基本类型或 void,则返回 null。
//        如果此对象表示一个数组类,则返回表示该 Object 类的 Class 对象。否则返回该类的超类。
    }

}

2.2 通配符

泛型中的通配符用于在两个类型之间建立某种类型的向上转型关系。

协变

? extends T, 例如List<? extends Fruit> list。确定了元素类型的父类为Fruit,但不能确定其确切类型,因此不能往该容器添加新的元素(只能添加null)。但是可以从容器中提取元素,类型为Fruit。

逆变

? super T,例如List<? super Apple> list,确定了元素为Apple的父类,因为可以往容器中添加元素,但不能提取元素。

表 通配符的协变与逆变

public class CovarianceAndContravariance {

    private static class Fruit {}

    private static class Apple extends Fruit {}

    private static <T extends Apple> void setItem(List<? super Apple> list, T item) {
        list.add(item);
    }

    private static Fruit getItem(List<? extends Fruit> list,int pos) {
        return list.get(pos);
    }

    public static void main(String[] args) {
        List<? super Apple> list = new ArrayList<>();
        setItem(list,new Apple());
        List<? extends Fruit> list2 = Arrays.asList(new Fruit(),new Apple());
        Fruit item = getItem(list2, 0);
    }

}

2.2.1 无界通配符

无界通配符,例如List<?>, 其相当于List<? extends Object>,但不等价于List(相当于List<Object>)。其有两个作用:

  1. 告诉编译器,我用了泛型,只是还没确定哪个类型;
  2. 用于捕获类型。
public class CaptureGeneric {

    private static class Holder<T> {}

    private static <T> void fun1(Holder<T> holder) {
        System.out.println(holder);
    }

    private static void fun2(Holder<?> holder) {
        fun1(holder);
    }

    public static void main(String[] args) {
        Holder holder = new Holder(); //  原生类型
        fun1(holder); // 警告,Unchecked assignment:
        fun2(holder); // 不会警告,无边界通配符将不会这个原生类型的类型参数(Object)
    }

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/635301.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

k8s pv 一直是release状态

如下图所示&#xff0c;pv 一直是release状态 这个时候大家可能就会想到现在我的 PVC 被删除了&#xff0c;PV 也变成了 Released 状态&#xff0c;那么我重建之前的 PVC 他们不就可以重新绑定了&#xff0c;事实并不会&#xff0c;PVC 只能和 Available 状态的 PV 进行绑定。…

【华为】将eNSP导入CRT,并解决不能敲Tab问题

华为】将eNSP导入CRT&#xff0c;并解决不能敲Tab问题 eNSP导入CRT打开eNSP&#xff0c;新建一个拓扑右键启动查看串口号关联CRT成功界面 SecureCRT连接华为模拟器ensp,Tab键不能补全问题选择Options&#xff08;选项&#xff09;-- Global Options &#xff08;全局选项&#…

ORB-SLAM2从理论到代码实现(六):Tracking程序详解(上)

1. Tracking框架 Tracking线程流程框图&#xff1a; 各流程对应的主要函数 2. Tracking整体流程图 上面这张图把Tracking.cc讲的特别明白。 tracking线程在获取图像数据后&#xff0c;会传给函数GrabImageStereo、GrabImageRGBD或GrabImageMonocular进行预处理&#xff0c;这…

wordpress主题 ACG美化插件v3.4.2支持zibll主题7b2主题美化

独具一格的二次元风格&#xff0c;打造全新的子比美化方向 大部分代码均为CSS、JS做成插件只是为了方便懒人小白站长 后台全功能一览&#xff0c;大部分美化均为网上通用流传&#xff0c;

基于ucos-ii操作系统的生产者消费者-问题

目 录 第1章 题目分析. 1 1.1 生产者线程... 1 1.2 消费者线程... 1 1.3 缓冲区... 1 1.4 进程的同步与互斥... 1 第2章 解决方案. 2 2.1 总体方案... 2 2.2 生产者问题... 2 2.3 消费者问题... 3 2.4 进程问题... 5 第3章 实验结果. 6 3.1 运行结果... 6 3.2 结果分析... 8 第…

用kimi一键绘制《庆余年》人物关系图谱

《庆余年》里面人物关系复杂&#xff0c;如果能画出一个人物关系图谱&#xff0c;可以直观的理解其中人物关系&#xff0c;更好的追剧。 首先&#xff0c;用kimi下载庆余年的分集剧情&#xff0c;常见文章《AI网络爬虫&#xff1a;批量爬取电视猫上面的《庆余年》分集剧情》&am…

【Java面试】三、Redis篇(下)

文章目录 1、抢券场景2、Redis分布式锁3、Redisson实现分布式锁4、Redisson实现的分布式锁是可重入锁5、Redisson实现分布式锁下的主从一致性6、面试 1、抢券场景 正常思路&#xff1a; 代码实现&#xff1a; 比如优惠券数量为1。正常情况下&#xff1a;用户A的请求过来&a…

Centos7.9上安装Oracle 11gR2 RAC 三节点(ASMlib管理asm磁盘)

服务器规划 OS 规格 主机名 IP VIP private IP scanip centos 7.9 1C4G racdb01 192.168.40.165 192.168.183.165 192.168.40.16 192.168.40.200 centos 7.9 1C4G racdb02 192.168.40.175 192.168.183.175 192.168.40.17 192.168.40.200 centos 7.9 1C4G…

目前流行的前端框架有哪些?

目前流行的前端框架有很多&#xff0c;它们可以帮助开发者快速构建高质量的前端应用程序。本文将介绍一些目前比较受欢迎的前端框架&#xff0c;并分析它们的优缺点。 React React 是一个由 Facebook 开发的开源前端JavaScript库&#xff0c;用于构建用户界面&#xff0c;尤其…

基于Vue的图片文件上传与压缩组件的设计与实现

摘要 随着前端技术的发展&#xff0c;系统开发的复杂度不断提升&#xff0c;传统开发方式将整个系统做成整块应用&#xff0c;导致修改和维护成本高昂。组件化开发作为一种解决方案&#xff0c;能够实现单独开发、单独维护&#xff0c;并能灵活组合组件&#xff0c;从而提升开…

OSPF多区域组网实验(华为)

思科设备参考&#xff1a;OSPF多区域组网实验&#xff08;思科&#xff09; 技术简介 OSPF多区域功能通过划分网络为多个逻辑区域来提高网络的可扩展性和管理性能。每个区域内部运行独立的SPF计算&#xff0c;而区域之间通过区域边界路由器进行路由信息交换。这种划分策略适用…

Python 机器学习 基础 之 数据表示与特征工程 【分类变量】的简单说明

Python 机器学习 基础 之 数据表示与特征工程 【分类变量】的简单说明 目录 Python 机器学习 基础 之 数据表示与特征工程 【分类变量】的简单说明 一、简单介绍 二、数据表示与特征工程 数据表示 特征工程 三、分类变量 1、One-Hot编码&#xff08;虚拟变量&#xff09…

【ArcGIS微课1000例】0112:沿线(面)按距离或百分比生成点

文章目录 一、沿线生成点工具介绍二、线状案例三、面状案例一、沿线生成点工具介绍 位置:工具箱→数据管理工具→采样→沿线生成点 摘要:沿线或面以固定间隔或百分比创建点要素。 用法:输入要素的属性将保留在输出要素类中。向输出要素类添加新字段 ORIG_FID,并设置为输…

Vue进阶之Vue项目实战(三)

Vue项目实战 图表渲染安装echarts图表渲染器(图表组件)图表举例:创建 ChartsRenderer.vue创建 ChartsDataTransformer.ts基于 zrender 开发可视化物料安装 zrender画一个矩形画一个柱状图基于svg开发可视化物料svg小示例使用d3进行图表渲染安装d3基本使用地图绘制本地持久化拓…

Leetcode861. 翻转矩阵后的得分

Every day a Leetcode 题目来源&#xff1a;861. 翻转矩阵后的得分 解法1&#xff1a;贪心 对于二进制数来说&#xff0c;我们只要保证最高位是1&#xff0c;就可以保证这个数是最大的&#xff0c;因为移动操作会使得它取反&#xff0c;因此我们进行行变化的时候只需要考虑首…

深度学习:手撕 RNN(2)-RNN 的常见模型架构

本文首次发表于知乎&#xff0c;欢迎关注作者。 上一篇文章我们介绍了一个基本的 RNN 模块。有了 这个 RNN 模块后&#xff0c;就像搭积木一样&#xff0c;以 RNN 为基本单元&#xff0c;根据不同的任务或者需求&#xff0c;可以构建不同的模型架构。本节介绍的所有结构&#…

Glassnode 内容主管:「减半」后的市场「抑郁」

原文标题&#xff1a;《Finance Bridge: Post-Halving Blues》撰文&#xff1a;Marcin Miłosierny&#xff0c;Glassnode 内容主管编译&#xff1a;Chris&#xff0c;Techub News 文章来源香港Web3媒体Techun News 摘要&#xff1a; 每月简报&#xff1a;4 月&#xff0c;尽…

前端自动将 HTTP 请求升级为 HTTPS 请求

前端将HTTP请求升级为HTTPS请求有两种方式&#xff1a; 一、index.html 中插入meta 直接在首页 index.html 的 head 中加入一条 meta 即可&#xff0c;如下所示&#xff1a; <meta http-equiv"Content-Security-Policy" content"upgrade-insecure-requests&…

从零开始傅里叶变换

从零开始傅里叶变换 1 Overview2 傅里叶级数2.1 基向量2.2 三角函数系表示 f ( t ) f(t) f(t)2.2.1 三角函数系的正交性2.2.2 三角函数系的系数 2.3 复指数函数系表示 f ( t ) f(t) f(t)2.3.1 复指数函数系的系数2.3.2 复指数函数系的正交性 2.4 傅里叶级数总结 3 傅里叶变换…

C++BuilderXE 如何让listView按文件名数字排序而非字母排序

int m_nDataColSort0; bool IsAsctrue; void __fastcall TForm1::RzListView4Compare(TObject *Sender, TListItem *Item1, TListItem *Item2, int Data, int &Compare) { if(m_nDataColSort0) { //按列表第二列排序 //CompareCompareText(Item1->SubItems-…