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

【Java技术专题】「入门到精通系列教程」深入探索Java特性中泛型技术体系的原理和实战开发指南

深入探索Java特性中泛型技术体系的原理和实战开发指南

  • 前提介绍
  • 泛型的介绍
    • 泛型带来的好处
    • 泛型引发的问题
      • 错觉:泛型也可以实现多态
  • 泛型的特点
    • 类型擦除
      • 概念定义
      • 案例分析
      • 隐患问题
      • 开发要点
      • 类型擦除的过程
        • 案例代码
    • 实例分析
      • 类型转换问题
    • 通配符与上下界
      • 通配符
        • List<Object>的处理
        • 类型不确定问题
        • 抽象Object类型
        • 泛型上界
    • 类型系统
      • 多态的类型转换
      • 泛型无法实现多态
      • 两种类型的继承体系
        • 泛型类型转换继承规则
    • 开发自己的泛型类
  • 总结

前提介绍

开发人员在使用泛型时,常常会根据自己的直觉犯一些错误,本文旨在对 Java 泛型进行概括性说明,并提供一些帮助开发人员避免这些常见错误的建议。

泛型的介绍

Java泛型(generics)是JDK 5中引入的一项新特性,允许在定义类和接口时使用类型参数(type parameter)。声明的类型参数在使用时用具体的类型来替换。泛型最主要的应用是在JDK 5中的新集合类框架中。对于泛型概念的引入,开发社区的观点褒贬不一。

泛型带来的好处

从好的方面来说,泛型的引入可以解决之前集合类框架在使用过程中通常会出现的运行时类型错误问题,因为编译器可以在编译时发现很多明显的错误。然而,为了保证与旧有版本的兼容性,Java泛型的实现存在一些不够优雅的地方。这也是任何有历史的编程语言所需要承担的历史包袱。

未来的版本更新将会解决早期设计缺陷所带来的问题。

泛型引发的问题

错觉:泛型也可以实现多态

例如,如果一个方法接收 List 作为形式参数,尝试将一个 List 的对象作为实际参数传递进去,就会发现无法通过编译。

尽管从直觉上来看,Object 是 String 的父类,这种类型转换应该是合理的,但实际上这会产生隐含的类型转换问题,因此编译器直接禁止这样的行为。

泛型的特点

在Java中,泛型基本上都是在编译器层次实现的。

类型擦除

概念定义

正确理解泛型概念的首要前提是理解类型擦除(type erasure),在生成的Java字节代码中,不包含泛型中的类型信息。在使用泛型时加上的类型参数会在编译时被编译器去掉,这个过程称为类型擦除。

案例分析

例如,代码中定义的 List 和 List 等类型,在编译后都会变成 List。JVM看到的只是 List,而泛型附加的类型信息对JVM来说是不可见的。

隐患问题

Java编译器会在编译时尽可能地发现可能出错的地方,但仍无法避免在运行时出现类型转换异常的情况。类型擦除也是Java的泛型实现方式与C++模板机制实现方式之间的重要区别。

开发要点

很多泛型的特性要点都与这个类型擦除的存在有关,包括:

  • 泛型类并没有独立的 Class 类对象。例如,List.class 或 List.class 并不存在,只有 List.class 存在。

  • 静态变量是被泛型类的所有实例所共享的。对于声明为 MyClass 的类,访问其中的静态变量的方法仍然是 MyClass.myStaticVar。无论是通过 new MyClass 还是 new MyClass 创建的对象,它们都共享一个静态变量。

  • 泛型的类型参数不能用于 Java 异常处理的 catch 语句中。因为异常处理是由 JVM 在运行时刻进行的,而类型信息被擦除,JVM 无法区分两个异常类型 MyException 和 MyException。对于 JVM 来说,它们都是 MyException 类型的,因此无法执行与异常对应的 catch 语句。

类型擦除的过程

  • 首先会找到用于替换类型参数的具体类,通常是 Object 类。如果指定了类型参数的上界,则使用该上界。
  • 然后将代码中的类型参数替换为具体类,并删除所有类型声明(即删除<>中的内容)。

例如,T get() 方法声明将变为 Object get(),而 List 将变为 List。

在此过程中,可能需要生成一些桥接方法(bridge method),因为擦除类型后的类可能缺少某些必要的方法。

案例代码

例如,考虑以下代码:

 class MyString implements Comparable<String> {public int compareTo(String str) { return 0; } }

当类型信息被擦除之后,上述类的声明变成了 class MyString implements Comparable。但是这样的话,类 MyString 就会有编译错误,因为没有实现接口Comparable 声明的 int compareTo(Object)方法。这个时候就由编译器来动态生成这个方法。

实例分析

了解了类型擦除机制后,就会明白编译器承担了全部的类型检查工作。编译器禁止某些泛型使用方式,正是为了确保类型的安全性。下面以 List 和 List 为例具体分析:

public void inspect(List<Object> list) { for (Object obj : list) { System.out.println(obj); } list.add(1); //这个操作在当前方法的上下文是合法的
}
public void test() { List<String> strs = new ArrayList<String>(); inspect(strs); //编译错误
}

这段代码中,inspect方法接收List作为参数。如果在test方法中试图传入List,则会出现编译错误。

类型转换问题

假设这样的做法是允许的,那么在inspect方法中就可以通过list.add(1)来向集合中添加一个数字。这样在test方法看来,其声明为List的集合中却被添加了一个Integer类型的对象。这显然是违反类型安全原则的,某个时候肯定会抛出ClassCastException异常。

因此,编译器禁止这样的行为。编译器会尽可能地检查可能存在的类型安全问题。对于确定是违反相关原则的地方,会给出编译错误。当编译器无法判断类型的使用是否正确时,会给出警告信息。

通配符与上下界

在使用泛型类的时候,既可以指定一个具体的类型,如 List就声明了具体的类型是String,也可以用通配符?来表示未知类型,如 List<?>就声明了 List 中包含的元素类型是未知的。

通配符

通配符所代表的其实是一组类型,但具体的类型是未知的。List<?>所声明的就是所有类型都是可以的。但是List<?>并不等同于 List。

List的处理

List实际上确定了 List 中包含的是 Object 及其子类,在使用的时候都可以通过 Object 来进行引用。

类型不确定问题
  • 类型唯一性:List<?>则其中所包含的元素类型是不确定。其中可能包含的是 String,也可能是 Integer。如果它包含了 String的话,往里面添加 Integer 类型的元素就是错误的。

  • 实例声明未知:不能通过 new ArrayList<?>()的方法来创建一个新的 ArrayList 对象。因为编译器无法知道具体的类型是什么。

抽象Object类型

但是对于 List<?>中的元素确总是可以用 Object 来引用的,因为虽然类型未知,但肯定是Object 及其子类。考虑下面的代码:

public void wildcard(List<?> list) {list.add(1);//编译错误
}

如上所示,试图对一个带通配符的泛型类进行操作的时候,总是会出现编译错误。其原因在于通配符所表示的类型是未知的。

泛型上界

因为对于 List<?>中的元素只能用Object 来引用,在有些情况下不是很方便。在这些情况下,可以使用上下界来限制未知类型的范围。如 List<? extends Number> 说明List中可能包含的元素类型是Number及其子类,而List<? super Number>则说明List中包含的是 Number 及其父类。

当引入了上界之后,在使用类型的时候就可以使用上界类中定义的方法,比如访问 List<? extends Number>的时候,就可以使用 Number类的 intValue 等方法。

类型系统

在Java中,大家比较熟悉的是通过继承机制而产生的类型体系结构,那就是多态特性。

多态的类型转换

比如,String继承自Object,根据 Liskov替换原则 ,子类是可以替换父类的。当需要Object类的引用的时候,如果传入一个String对象是没有任何问题的。但是反过来的话,即用父类的引用替换子类引用的时候,就需要进行强制类型转换。

泛型无法实现多态

编译器并不能保证运行时刻这种转换一定是合法的。这种自动的子类替换父类的类型转换机制,对于数组也是适用的。String[]可以替换Object[]。但是泛型的引入,对于这个类型系统产生了一定的影响。正如前面提到的List是 不能替换掉List的。

两种类型的继承体系

引入泛型之后的类型系统增加了两个维度:一个是类型参数自身的继承体系结构,另外一个是泛型类或接口自身的继承体系结构。

  1. List和List这样的情况,类型参数String是继承自 Object 的。
  2. List接口继承自Collection 接口。
泛型类型转换继承规则
  • 相同类型参数的泛型类的关系取决于泛型类自身的继承体系结构,即List 是 Collection 的子类型, List 可以替换Collection。这种情况也适用于带有上下界的类型声明。

  • 当泛型类的类型声明中使用了通配符的时候,其子类型可以在两个维度上分别展开。

    • 对 Collection<? extends Number>来说,其子类型可以在Collection这个维度上展开,即List<? extends Number>和 Set<? extends Number>等;
    • 在Number 这个层次上展开,即 Collection和 Collection等。如此循环下去,ArrayList和 HashSet等也都算是 Collection<? extends Number>的子类型。
  • 如果泛型类中包含多个类型参数,则对于每个类型参数分别应用上面的规则。

理解了上面的规则之后,就可以很容易的修正实例分析中给出的代码了。只需要把List改成 List<?>即可。List是 List<?>的子类型,因此传递参数时不会发生错误。

开发自己的泛型类

泛型类与一般的 Java 类基本相同,只是在类和接口定义上多出来了用<>声明的类型参数。一个类可以有多个类型参数,如 MyClass<X, Y, Z>。 每个类型参数在声明的时候可以指定上界。所声明的类型参数在 Java 类中可以像一般的类型一样作为方法的参数和返回值,或是作为域和局部变量的类型。但是由于类型擦除机制,类型参
数并不能用来创建对象或是作为静态变量的类型。考虑下面的泛型类中的正确和错误的用法。

class ClassTest<X extends Number, Y, Z> { private X x; private static Y y; //编译错误,不能用在静态变量中 public X getFirst() {//正确用法 return x; } public void wrong() { Z z = new Z(); //编译错误,不能创建对象 }
}

总结

Java泛型为我们提供了一种更加类型安全和灵活的编程方式,但也需要我们在使用时注意其一些局限性和设计缺陷。

  • 建议在代码中避免泛型类和原始类型的混用,例如List和List不应该共同使用,因为这样会产生一些编译器警告和潜在的运行时异常。如果需要利用JDK 5之前开发的遗留代码,也应尽可能隔离相关的代码。

  • 在使用带通配符的泛型类时,需要明确通配符所代表的一组类型的概念。由于具体的类型是未知的,很多操作是不允许的。

  • 最好不要将泛型类与数组一起使用。虽然可以创建new List<?>[10]这样的数组,但无法创建new List[10]这样的数组。这限制了数组的使用能力,而且会带来很多费解的问题。因此,在需要类似数组的功能时,建议使用集合类。

  • 请不要忽视编译器给出的警告信息,这些警告信息可能会指出代码中存在的潜在问题,及时修复可以避免后续出现更严重的问题。

相关文章:

【Java技术专题】「入门到精通系列教程」深入探索Java特性中泛型技术体系的原理和实战开发指南

深入探索Java特性中泛型技术体系的原理和实战开发指南 前提介绍泛型的介绍泛型带来的好处泛型引发的问题错觉&#xff1a;泛型也可以实现多态 泛型的特点类型擦除概念定义案例分析隐患问题开发要点类型擦除的过程案例代码 实例分析类型转换问题 通配符与上下界通配符List<Ob…...

基于Java SSM邮局订报管理系统

尽管电子读物越来越普及&#xff0c;但还是有很多读者对纸质刊物情有独钟&#xff0c;所以邮局的报刊征订业务一直非常受欢迎。邮局订报管理系统就是对客户在邮局订阅报刊进行管理&#xff0c;包括查询报刊、订阅报刊、订阅信息的查询、统计等的处理&#xff0c;系统的主要业务…...

【优选算法系列】【专题一双指针】第四节.15. 三数之和和18. 四数之和

文章目录 前言一、三数之和 1.1 题目描述 1.2 题目解析 1.2.1 算法原理 1.2.2 代码编写 1.2.3 题目总结二、四数之和 2.1 题目描述 2.2 题目解析 2.2.1 算法原理 2.2.2 代码编写 …...

字符集——带你了解UTF-8的前世今生

文章目录 字符集的来历汉字和字母的编码特点Unicode字符集字符集小结编码和解码开发约定 字符集的来历 计算机是美国人发明的&#xff0c;由于计算机能够处理的数据只能是0和1组成的二进制数据&#xff0c;为了让计算机能够处理字符&#xff0c;于是美国人就把他们会用到的每一…...

数据分析工具比较:Excel vs Python vs R

写在开头 在数据分析的世界里&#xff0c;选择合适的工具至关重要。本篇博客将深入比较常用的数据分析工具&#xff0c;包括Excel、Python和R&#xff0c;以帮助读者更好地选择适合自己需求的工具。 1.Excel&#xff1a;经典易用的电子表格 优势&#xff1a; 用户友好&…...

Java基础数据类型

Java有八种基础的数据类型&#xff0c;它们被分为两个主要的类别&#xff1a;原始类型和引用类型。原始类型又被分为四类&#xff1a;整型、浮点型、字符型和布尔型。 整型&#xff08;Integral Types&#xff09;&#xff1a; 这些类型用于存储整数。它们包括&#xff1a; ○…...

Linux-Linux安装JDK及配置环境 及 遇到的问题

下载linux环境对应的JDK的tar.gz包 配置JDK环境&#xff1a;编辑 sudo vim /etc/profile 在文件的最下方&#xff0c;填写 export JAVA_HOME/usr/local/src/software/jdk1.8 export CLASSPATH.:$JAVA_HOME/lib/tools.jar export PATH$JAVA_HOME/bin:$PATH 执行生效命令&…...

后端架构的一些知识

目录 一.抖音 二.大型网站是如何管理海量的数据的 三.大型网站停机一天会造成多大损失 四.如何设计一套安全&#xff0c;健壮&#xff0c;可扩展&#xff0c;稳定性强的后端系统 五.如何在不影响原来代码的基础上进行功能更新 六.大型网站一年都不停机吗 七.线上业务出现…...

golang使用sip实现语音通话

在使用 github.com/cloudwebrtc/sip 这个 Go 语言库时&#xff0c;要实现通话&#xff0c;您需要处理 SIP 协议的一系列操作&#xff0c;包括建立和终止呼叫、处理媒体传输等。以下是一个简化的示例代码&#xff0c;演示如何使用该库来处理 SIP 通话的基本流程&#xff1a; pac…...

【1day】蓝凌OA 系统custom.jsp 接口任意文件读取漏洞学习

注:该文章来自作者日常学习笔记,请勿利用文章内的相关技术从事非法测试,如因此产生的一切不良后果与作者无关。 目录 一、漏洞概述 二、影响版本 三、资产测绘 四、漏洞复现...

OWASP Web 安全测试指南-Web 应用程序安全测试

Web 应用程序安全测试 4.0 简介和目标 4.1 信息收集 4.2 配置和部署管理测试 4.3 身份管理测试 4.4 身份验证测试 4.5 授权测试 4.6 会话管理测试 4.7 输入验证测试 4.8 错误处理测试 4.9 弱密码测试 4.10 业务逻辑测试 4.11 客户端测试 4.0 简介和目标 本节介绍 O…...

oracle FUNCTION(任意两个时间 之间的工作小时)

写函数计算 任意两个时间 之间的工作小时 每天工作时间&#xff08;8:00 - 20:00 共12小时&#xff09;&#xff0c;没有休息日 CREATE OR REPLACE FUNCTION SC_YD_DESI.CALCULATE_WORK_HOURS_FUNC (p_current_time IN DATE,p_order_time IN DATE ) RETURN NUMBER ASp_work_hou…...

【“C++ 精妙之道:解锁模板奇谭与STL精粹之门“】

【本节目标】 1. 泛型编程 2. 函数模板 3. 类模板 4. 什么是STL 5. STL的版本 6. STL的六大组件 7. STL的重要性 8. 如何学习STL 9.STL的缺陷 1. 泛型编程 如何实现一个通用的交换函数呢&#xff1f; void Swap(int& left, int& right) {int temp left;lef…...

el-date-picker时间控制范围为过去时间不可选

<el-date-picker :picker-options"startPickerOptions()" value-format"yyyy-MM-dd HH:mm:ss" v-model"form.applyFixPlan" type"datetime" placeholder"选择日期时间"> </el-date-picker> 在method中定义star…...

音视频技术开发周刊 | 322

每周一期&#xff0c;纵览音视频技术领域的干货。 新闻投稿&#xff1a;contributelivevideostack.com。 超级AI不会主宰人类&#xff0c;但人工智能必须开源&#xff01;LeCun最新采访引全网300万人围观 LeCun最新访谈视频中&#xff0c;再次坦露了自己对开源AI的看法。超级AI…...

面试就是这么简单,offer拿到手软(三)—— 常见中间件框架面试题,es,redis,dubbo,zookeeper kafka 等

面试就是这么简单&#xff0c;offer拿到手软&#xff08;一&#xff09;—— 常见非技术问题回答思路 面试就是这么简单&#xff0c;offer拿到手软&#xff08;二&#xff09;—— 常见65道非技术面试问题 面试就是这么简单&#xff0c;offer拿到手软&#xff08;三&#xff…...

【Spring系列】DeferredResult异步处理

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...

使用晶振遇到的两个问题

并联电阻的问题 在一些方案中&#xff0c;晶振并联1MΩ电阻时&#xff0c;程序运行正常&#xff0c;而在没有1MΩ电阻的情况下&#xff0c;程序运行有滞后及无法运行现象发生。 原因分析&#xff1a; 在无源晶振应用方案中&#xff0c;两个外接电容能够微调晶振产生的时钟频率…...

手写promise A+、catch、finally、all、allsettled、any、race

目录 手写promise 同步版 1.Promise的构造方法接收一个executor()&#xff0c;在new Promise()时就立刻执行executor回调 2.executor()内部的异步任务被放入宏/微任务队列&#xff0c;等待执行 3.状态与结果的管理 状态只能变更一次 4.then()调用成功/失败回调 catch是…...

【原神游戏开发日志1】缘起

【原神游戏开发日志1】缘起 版权声明 本文为“优梦创客”原创文章&#xff0c;您可以自由转载&#xff0c;但必须加入完整的版权声明 文章内容不得删减、修改、演绎 相关学习资源见文末 大家好&#xff0c;最近看到原神在TGA上频频获奖&#xff0c;作为一个14年经验的游戏开…...

leetcode5 最长公共前缀三种python解法

14. 最长公共前缀 编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀&#xff0c;返回空字符串 ""。 示例 1&#xff1a; 输入&#xff1a;strs ["flower","flow","flight"] 输出&#xff1a;"fl"示…...

对小程序的初了解

WXML和HTML的区别 标签名称不同 HTML&#xff1a;div、a、span、img WXML&#xff1a;view、text、image、navigator 属性节点不同 <a href"#">超链接</a> <navigator url"/pages/home/home"></navigator> 提供了类似vue的…...

QLineEdit 的 InputMask掩码

QLineEdit 的 InputMask掩码 A&#xff1a;只能输入字母&#xff0c;且不可省略 a&#xff1a;只能输入字母&#xff0c;可以省略 N&#xff1a;只能输入 字母和数字&#xff0c;且不可省略 n&#xff1a;只能输入 字母和数字&#xff0c;可以省略 X&#xff1a;可以输入任意字…...

关于队列的简单理解

1.队列(Queue) 1.1 关于队列 队列 &#xff1a;只允许在一端进行插入数据操作&#xff0c;在另一端进行删除数据操作的特殊线性表&#xff0c; 队列具有先进先出 FIFO(First In First Out)的操作特性&#xff08;队列是个接口&#xff09;&#xff1b; 入队列&#x…...

加密市场进入牛初阶段?一场新的造富效应即将拉开帷幕!

周一(12月4日)&#xff0c;比特币一度上涨至42000美元&#xff0c;创下自2022年4月以来的最高水平。从目前比特币的走势来看&#xff0c;加密市场无疑已然进入到牛初阶段。 在牛市初期&#xff0c;确实存在人们不相信牛市到来的情况。由于在熊市中亏损的心理阻碍和对市场进一步…...

Superset基础入门

1 Superset概述 Apache Superset 是一个现代的数据探索和可视化平台。它功能强大且十分易用&#xff0c;可对接 各种数据源&#xff0c;包括很多现代的大数据分析引擎&#xff0c;拥有丰富的图表展示形式&#xff0c;并且支持自定义 仪表盘。 2 Superset安装 Superset 是由 P…...

【泛微ecology】将多个字段的数据合并到一个字段

doFieldSQL("select concat(concat(sqr,,),sy) as c from formtable_main_2 where requestid $requestid$ ")...

WebSocket入门介绍及编程实战

HTTP的限制 全双工和半双工&#xff1a; 全双工&#xff1a;全双工&#xff08;Full Duplex&#xff09;是允许数据在两个方向上同时传输。 半双工&#xff1a;半双工&#xff08;Half Duplex&#xff09;是允许数据在两个方向上传输&#xff0c;但是同一个时间段内只允许一个…...

vue3里面生命周期的使用

前言&#xff1a; vue2里面的生命周期和vue3生命周期是非常的相似的&#xff0c;我们通过访问生命周期钩子来处理不同场景之间的应用。 生命周期钩子的函数定义&#xff1a;每一个Vue组件实例在创建时都需要经历一系列的初始化步骤&#xff0c;比如数据侦听&#xff0c;编译模…...

在python的Scikit-learn库中,可以使用train_test_split函数来划分训练集和测试集。

文章目录 一、在Scikit-learn库中&#xff0c;可以使用train_test_split函数来划分训练集和测试集总结 一、在Scikit-learn库中&#xff0c;可以使用train_test_split函数来划分训练集和测试集 在Scikit-learn库中&#xff0c;可以使用train_test_split函数来划分训练集和测试…...

建设报名系统官方网站/seo网站推广什么意思

IoC&#xff0c;直观地讲&#xff0c;就是容器控制程序之间的关系&#xff0c;而非传统实现中&#xff0c;由程序代码直接操控。这也就是所谓“控制反转”的概念所在。控制权由应用代码中转到了外部容器&#xff0c;控制权的转移是所谓反转。IoC还有另外一个名字——“依赖注入…...

长沙市住房和建设委员会网站/网络营销环境

在阎宏博士的《JAVA与模式》一书中开头是这样描述责任链&#xff08;Chain of Responsibility&#xff09;模式的&#xff1a; 责任链模式是一种对象的行为模式。在责任链模式里&#xff0c;很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递&#…...

wordpress更改后台/网站百度关键词排名软件

Linux作系统实验四淮海工学院计算机工程学院实验报告书课程名&#xff1a;《网络操作系统》题 目&#xff1a; SHELL程序设计班 级&#xff1a; 网路122学 号&#xff1a;姓 名&#xff1a;一、目的与要求掌握SHELL程序设计方法和技术。二、实验内容与题目用Shell编程&#xff…...

广东深广东深圳网站建设服务/手机打开国外网站app

点击上方蓝色字体&#xff0c;选择“标星公众号”优质文章&#xff0c;第一时间送达上一篇&#xff1a;这300G的Java资料是我师傅当年给我的&#xff0c;免费分享给大家&#xff08;已修复&#xff09;下一篇&#xff1a;昨天分享资料不小心把百度网盘深处的秘密泄露了&#xf…...

51自学网网站开发/百度客户管理系统登录

欢迎访问网易云社区&#xff0c;了解更多网易技术产品运营经验。2018年9月&#xff0c;网易云易盾宣布&#xff0c;与智能和自动化网络安全解决方案提供商A10 Networks结成战略合作伙伴关系。双方将在抗DDoS攻击领域展开深入合作&#xff0c;共同推出深度集成的联合解决方案&am…...

网站的布局有哪些/建一个网站大概需要多少钱

github文档地址&#xff1a; https://github.com/yimijianfang/vue-drag-verify 转载自&#xff08;http://www.jq22.com/jquery-info22779&#xff09;注意用的时候需要对父元素或html设置 user-select: none 效果1 实现方法 1 新建一个 vue 文件 将以下代码复制进去 <…...