关于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 有何不同? 简介 前两篇文章介绍了泛型的基本用法.类型擦除以及泛型数组.在泛型的使用中,还有个重要的 ...
随机推荐
- 查看Dubbo服务-通过zk客户端
一.基本概念 https://www.cnblogs.com/huasky/p/8268568.html 二.下载与安装 1.进入要下载的版本的目录,选择.tar.gz文件下载 下载链接:http:/ ...
- Java——ArrayList底层源码分析
1.简介 ArrayList 是最常用的 List 实现类,内部是通过数组实现的,它允许对元素进行快速随机访问.数组的缺点是每个元素之间不能有间隔, 当数组大小不满足时需要增加存储能力,就要将已经有数 ...
- Vue 2.0 入门系列(15)学习 Vue.js 需要掌握的 es6 (2)
类与模块 类 es6 之前,通常使用构造函数来创建对象 // 构造函数 User function User(username, email) { this.username = username; ...
- Spring框架 课程笔记
Spring框架 课程笔记 第1章 Spring概述 1.1 Spring概述 1) Spring是一个开源框架 2) Spring为简化企业级开发而生,使用Spring ...
- python中虚拟环境virtualenvwrapper的安装和使用
虚拟环境为什么需要虚拟环境: 到目前为止,我们所有的第三方包安装都是直接通过 pip install xx 的方式进行安装的,这样安装会将那个包安装到你的系统级的 Python 环境中.但 ...
- java复习(6)String、StringBuffer以及StringBuilder
0.常见的编码表 ASC||:美国标准信息交换码,用一个字节的7位可以表示. ISO8859-1:拉丁码表.欧洲码表,用一个字节的8位来表示.无法存储汉字,或者只取了汉字的一半使用 GB2312:中文 ...
- spring多个context:property-placeholder不生效问题
先来看下A和B两个模块,A模块和B模块都分别拥有自己的Spring XML配置,并分别拥有自己的配置文件: A模块的Spring配置文件如下: <?xml version="1.0&q ...
- 33. Search in Rotated Sorted Array (JAVA)
Implement next permutation, which rearranges numbers into the lexicographically next greater permuta ...
- ie10兼容问题 -- 将div定位absolute在图片img上面,导致div点击事件无效
ie10兼容问题: 将div定位absolute在图片img上面,发现div若不加背景色,导致div点击事件(任何事件)无效. <div class="paper-box"& ...
- Java并发——原子变量和原子操作
很多情况下我们只是需要一个简单的.高效的.线程安全的递增递减方案.注意,这里有三个条件:简单,意味着程序员尽可能少的操作底层或者实现起来要比较容易:高效意味着耗用资源要少,程序处理速度要快:线程安全也 ...