关于Java泛型实现原理的思考与一般用法示例总结
面向对象的一个重要目标是对代码重用的支持。支持这个目标的一个重要机制就是泛型机制。在1.5版本之前,java并没有直接支持泛型实现,泛型编程的实现时通过使用继承的一些基本概念来完成的。
这种方式的局限性有:
1. 使用此种方式会不可避免地用到强制类型转换。
2.
不能使用基本类型,只有引用类型能和Object相容。(通过使用包装器类)
例如使用Comparable接口来暂时代表所有实现了该接口的类。
什么是协变性?
简而言之,如果A IS-A B,那么A[] IS-A B[]。
举例:现在有类型Person、Employee和Student。Employee
是一个(IS-A) Person,Student是一个(IS-A)Person。那么下面的语句可以通过编译:
但是上面的代码在运行时却会出错。因为arr[0]实际上是引用一个Employee,可是Student IS-NOT-A
Employee。这样就产生了混乱。这种错误正是由于Java数组的协变性而产生的。那么Java为什么不禁止数组协变呢?
因为SE5之前还没有泛型,但很多代码迫切需要泛型来解决问题。 例如:
Arrays.equals()方法的底层实现调用的是Object.equals()方法,和数组中元素的具体类型无关,这充分利用了Java中任何类型都继承自Object类的特性,避免了为每个类型都重新定义Arrays.equals()方法。而在没有泛型的时代,要让Object[]能接受所有数组类型,最简单的办法就是让数组接受协变,把String[],Integer[]都定义成Object[]的派生类,然后多态就起作用了。
为什么数组设计成”协变“不会有大问题呢?
这是基于数组的一个独有特性:
数组记得它内部元素的具体类型,并且会在运行时做类型检查。
因为arr[0]记得它内部的元素类型是Employee,所以运行时给它插入一个Student类型会报错。
这个特性使得Java数组协变带来的影响不会酿成大错——错误最终还是会被检测出来,只不过是从编译时推迟到了运行时。
正是有这个特性,Java当初才敢于把数组设计成协变的。虽然向上转型以后,编译期类型检查放松了,但因为数组运行时对内部元素类型的严格检查,不匹配的类型还是插不进去的。这也是为什么容器Collection不能设计成协变的原因——Collection不做运行时类型检查。
简单泛型类
简单泛型接口
如果一个int型量被传递到需要一个Integer对象的地方,那么,编译器将在幕后插入一个对Integer构造方法的调用以获得Integer对象。这就叫做自动装箱。
反过来,如果一个Integer对象被放到需要int型量的地方,则编译器将在幕后插入一个对intValue方法的调用以获得int值,这就叫做自动拆箱。
对于其他7对基本类型/包装类型,同样会发生类似的情形。
Java7增加了一种新特性,称作菱形运算符。使得下面的代码:
可以写成:
菱形运算符在不增加开发者负担的情况下简化了代码。
在Java中,数组是协变的,如果B IS-A C,那么B[] IS-A C[]
。但是集合Collection不是协变的,这就使得集合缺少灵活性。为了弥补这个不足,Java5引入了通配符。举例如下
既然有那么也有与之对应的,他们分别为泛型参数的上界和下界。
1.泛型类
2.泛型方法
泛型方法分为两种,区别在于是否带特定参数列表。
- 一、 不带特定参数列表的泛型方法
因为它能接受不同类型的参数,所以,它是泛型方法。
- 二、带特定参数列表的泛型方法
通过在返回类型前声明特定参数,能够获得以下好处:
- 可以将T用作返回类型
- 不止一个方法参数的声明需要用到T
- 将T用于声明局部变量
泛型方法与泛型类很相似,因为参数列表使用相同的语法。在泛型方法中,泛型参数的声明在返回类型之前,而泛型类在类名之后。
一、什么是类型擦除
让我们看一个有趣的例子:
和很明显是不同的类型。但是上面的程序却认为它们是相同的类型。因此,存在一个残酷的现实:
在泛型代码内部,无法获得任何有关泛型参数类型的信息。
Java泛型是使用擦除来实现的,这意味着当你在使用泛型的时候,任何具体的类型信息都被擦除了,你唯一知道的就是你在使用一个对象。因此和在运行时事实上是相同的类型。这两种类型都被擦除成了他们的“原生”类型,即List。
再看另外一个例子:
类似的代码,在c++中能够正常运行。但是由于类型擦除,Java编译器无法将useF()能够在obj上调用f()这一需求映射到A拥有f()这一事实上。在类B中,由于T没有指定上下界,于是T只拥有默认的上界Object(等同于),因此在泛型方法中我们只能调用Object的方法。类似的,如果我们想要在泛型方法中使用泛型参数的方法,那么我们必须设定上下界。下面的代码就可以编译了:
边界声明T必须具有类型A或者从A导出的类型。
泛型参数将擦除到它的第一个边界。编译器实际上会把类型参数替换为它的擦除。就像上面的示例那样,T擦除到了A。.
二、怎么看待类型擦除
类型擦除不是一个语言特性,它只是一种折中。因为Java1中没有泛型,泛型是后来加入的。为了不影响现有的类库和已经在使用的代码,Java没有采用C++等语言那样彻底的泛型,而是使用类型擦除这种温和但有缺陷的方式类让Java拥有泛型。
擦除的代价是明显的。泛型不能用于显示地引用运行时类型的操作之中,例如转型、instanceof操作和new表达式。因为所有关于参数的类型信息都丢失了,无论何时,当你在编写泛型代码时,必须时刻提醒自己,雅思考试报名官网你只是看起来拥有有关参数的类型信息而已,你实际操作的只是一个Object。
- 基本类型不能用作类型参数,类型参数必须是引用类型。必须使用包装类。
- 不能用instanceof检测泛型,由于类型擦除的原因,任何泛型类型都会别擦除为它的原生类型。
- 泛型类中,static方法和static域不能引用类的泛型变量。因为在类型擦除过后,类型参数就不存在了。同时,我们知道同一个类的所有实例共用类的static域和static方法,如果静态域接受泛型参数,那么这个参数到底是类型参数的哪一种实际类型就无法确定了。
- 不能创建一个泛型类型的实例。是非法的。
- 不能创建一个泛型数组,因为数组有严格的类型检查。通常用泛型容器,如来实现同样的需求。
关于Java泛型实现原理的思考与一般用法示例总结的更多相关文章
- Java泛型-内部原理: 类型擦除以及类型擦除带来的问题
一:Java泛型的实现方法:类型擦除 大家都知道,Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦掉,正确理解泛型概念的首要前提是理解类型擦除.Java的泛型基本上都是在编译 ...
- 关于Java泛型"擦除"的一点思考
头次写博客,想说的东西不难,关于泛型的疑问,是前一阵在学习jackson中遇到的. 下面就把我所想到的.遇到的,分享出来. 泛型是JDK1.5后的一个特性,是一个参数类型的应用,可以将这个参数声明在类 ...
- JAVA泛型实现原理
1. Java范型时编译时技术,在运行时不包含范型信息,仅仅Class的实例中包含了类型参数的定义信息.泛型是通过java编译器的称为擦除(erasure)的前端处理来实现的.你可以(基本上就是)把它 ...
- java 泛型实现原理
泛型思想最早在C++语言的模板(Templates)中产生,Java后来也借用了这种思想.虽然思想一致,但是他们存在着本质性的不同. C++中的模板是真正意义上的泛型,在编译时就将不同模板类型参数编译 ...
- java SequenceInputStream类(序列输入流)的用法示例
public class SequenceInputStreamextends InputStream SequenceInputStream 表示其他输入流的逻辑串联.它从输入流的有序集合开始,并从 ...
- java泛型学习(2)
一:深入泛型使用.主要是父类和子类存在泛型的demo /** * 父类为泛型类 * @author 尚晓飞 * @date 2014-7-15 下午7:31:25 * * * 父类和子类的泛型. * ...
- Java泛型总结---基本用法,类型限定,通配符,类型擦除
一.基本概念和用法 在Java语言处于还没有出现泛型的版本时,只能通过Object是所有类型的父类和类型强制转换两个特点的配合来实现类型泛化.例如在哈希表的存取中,JDK1.5之前使用HashMap的 ...
- 浅析Java泛型
什么是泛型? 泛型是JDK 1.5的一项新特性,它的本质是参数化类型(Parameterized Type)的应用,也就是说所操作的数据类型被指定为一个参数,在用到的时候在指定具体的类型.这种参数类型 ...
- Java 泛型 <? super T> 中 super 怎么 理解?与 < ? extends T>有何不同?
Java 泛型 <? super T> 中 super 怎么 理解?与 extends 有何不同? 简介 前两篇文章介绍了泛型的基本用法.类型擦除以及泛型数组.在泛型的使用中,还有个重要的 ...
随机推荐
- [转帖]TLS握手:回顾1.2、迎接1.3
TLS握手:回顾1.2.迎接1.3 novsec2019-05-10共26541人围观 ,发现 2 个不明物体网络安全 *本文原创作者:novsec,本文属于FreeBuf原创奖励计划,未经许可禁止转 ...
- Mybatis-学习笔记(6)Mybatis的事务管理机制
1.什么是事务. 多个数据库原子访问应该被绑定成一个整体,这就是事务.事务是一个最小的逻辑执行单元,整个事务不能分开执行,要么同时执行,要么同时放弃执行. 事务的4个特性:原子性.一致性.隔离性.持续 ...
- RabbitMq学习6-安装php-amqplib(RabbitMQ的phpAPI)
一.使用composer安装php-amqplib 1.在你的项目中添加一个 composer.json文件: { "require": { "php-amqplib/p ...
- git的配置设置
git的基本配置 git是一个版本控制工具,既然是工具,那么就可以根据人的个人喜好来进行设置,git也提供了配置,可以根据自己的喜好来对它进行个性化的设计,以让自己舒服的玩. git有三个配置文件 / ...
- socket 服务器向指定的客户端发消息
一.需求 需求如题. 当多个客户端连接服务器时,服务器如何给指定的客户端发送消息. 二.解决方案 核心思想: 在服务器端,需保存不同客户端的socket列表及客户端相关信息. socket含有发送方和 ...
- <img> 标签的 src 属性
src属性 加载的时候就会请求 1.servlet生成一个图片 2.你直接输入servlet的连接看一下,就是一个图片,和我们自己发布到服务器的一样. 3.页面加载时,会访问这个servelt连接,自 ...
- 微服务框架学习二:Http调用
1. HTTP接口的意义 二进制接口使用的是java/hessian序列化协议,不能很好的与其他语言通信,虽然hessian也是一种跨语言的通用协议,但很多语言没有很好的实现该协议的产品.所以为了能够 ...
- apachectl 命令详解-graceful 不中断原有连接,重新启动 Apache 服务器
apachectl(Apache control interface) 参 数: fullstatus 显示服务器完整的状态信息. graceful 重新启动 Apac ...
- AlphaStar: Mastering the Real-Time Strategy Game StarCraft II 博客要点
original blog: https://deepmind.com/blog/alphastar-mastering-real-time-strategy-game-starcraft-ii S ...
- Qt中添加自定义信号和槽带来的一些问题
背景: 自己定义了一个类,并在类中添加了槽函数 class XImage : public QWidget { public: XImage(QWidget *p = 0); //重载绘制方法 upd ...