关于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 有何不同? 简介 前两篇文章介绍了泛型的基本用法.类型擦除以及泛型数组.在泛型的使用中,还有个重要的 ...
随机推荐
- 关于Java多线程的一些面试问题
1.ArrayList和Vecoter区别? Array和ArrayList的异同点一.Array和ArrayList的区别#1. Array类型的变量在声明的同时必须进行实例化(至少得初始化数组的大 ...
- java基础语法详细介绍
一.概述 1.java语言概述 是SUN(Stanford University Network,斯坦福大学网络公司 ) 1995年推出的一门高级编程语言; java之父---James Goslin ...
- ambari 2.5.0源码编译安装
参考:https://www.ibm.com/developerworks/cn/opensource/os-cn-bigdata-ambari/index.html Ambari 是什么 Ambar ...
- Antd时间选择框汉化问题总结------国际化全局设置
import zh_CN from 'antd/lib/locale-provider/zh_CN'; import 'moment/locale/zh-cn'; import { ConfigPro ...
- iOS之Run Loop详解
转自标哥的技术博客(www.henishuo.com) 前言 做了一年多的IOS开发,对IOS和Objective-C深层次的了解还十分有限,大多还停留在会用API的级别,这是件挺可悲的事情.想学好一 ...
- python中的面向对象和面向过程
一.面向对象和面向过程 一.什么是面向过程 核心是过程二字:面向过程是一种流水线的工作流程,是先做什么在做什么 二.什么是面向对象 核心 是对象二字:是一种编程思想,上帝思维,一切皆对象,编程中负责调 ...
- 磁盘IO性能优化-实践
RAID卡缓存策略调整 原因详解 操作实例 I/O 调度算法 文件系统journal 磁盘挂载参数 操作实例 性能数据对比 RAID卡缓存策略调整 可以将RAID卡缓存策略由No Write Cach ...
- 10年前文章_respin 下制作iso 文件的脚本说明
1.prepare_spin.sh 用于在 /var/rpm 下生成 lhs-local 需要的repositery 2.respin.sh 使用revisor 生成 iso 3. post_spi ...
- python 导入模块、包
1. 模块:一个有逻辑的python文件,包含变量.函数.类等.2. 包:一个包含__init__.py的文件夹,存放多个模块 import 本质是路径搜索,查找sys.path下有无你导入的 pac ...
- java ThreadGroup源码分析
使用: import javax.swing.text.html.HTMLDocument.HTMLReader.IsindexAction; public class Test { public s ...