类型擦除

代码片段一

Class c1 = new ArrayList<Integer>().getClass();
Class c2 = new ArrayList<String>().getClass();
System.out.println(c1 == c2); /* Output
true
*/

显然在平时使用中,ArrayList<Integer>()new ArrayList<String>()是完全不同的类型,但是在这里,程序却的的确确会输出true

这就是Java泛型的类型擦除造成的,因为不管是ArrayList<Integer>()还是new ArrayList<String>(),都在编译器被编译器擦除成了ArrayList。那编译器为什么要做这件事?原因也和大多数的Java让人不爽的点一样——兼容性。由于泛型并不是从Java诞生就存在的一个特性,而是等到SE5才被加入的,所以为了兼容之前并未使用泛型的类库和代码,不得不让编译器擦除掉代码中有关于泛型类型信息的部分,这样最后生成出来的代码其实是『泛型无关』的,我们使用别人的代码或者类库时也就不需要关心对方代码是否已经『泛化』,反之亦然。

在编译器层面做的这件事(擦除具体的类型信息),使得Java的泛型先天都存在一个让人非常难受的缺点:

在泛型代码内部,无法获得任何有关泛型参数类型的信息。

代码片段二

List<Integer> list = new ArrayList<Integer>();
Map<Integer, String> map = new HashMap<Integer, String>();
System.out.println(Arrays.toString(list.getClass().getTypeParameters()));
System.out.println(Arrays.toString(map.getClass().getTypeParameters())); /* Output
[E]
[K, V]
*/

关于getTypeParameters()的解释:

Returns an array of TypeVariable objects that represent the type variables declared by the generic declaration represented by this GenericDeclaration object, in declaration order. Returns an array of length 0 if the underlying generic declaration declares no type variables.

我们期待的是得到泛型参数的类型,但是实际上我们只得到了一堆占位符。

代码片段三

public class Main<T> {

    public T[] makeArray() {
// error: Type parameter 'T' cannot be instantiated directly
return new T[5];
}
}

我们无法在泛型内部创建一个T类型的数组,原因也和之前一样,T仅仅是个占位符,并没有真实的类型信息,实际上,除了new表达式之外,instanceof操作和转型(会收到警告)在泛型内部都是无法使用的,而造成这个的原因就是之前讲过的编译器对类型信息进行了擦除。

同时,面对泛型内部形如T var;的代码时,记得多念几遍:它只是个Object,它只是个Object……

代码片段四

public class Main<T> {

    private T t;

    public void set(T t) {
this.t = t;
} public T get() {
return t;
} public static void main(String[] args) {
Main<String> m = new Main<String>();
m.set("findingsea");
String s = m.get();
System.out.println(s);
}
} /* Output
findingsea
*/

虽然有类型擦除的存在,使得编译器在泛型内部其实完全无法知道有关T的任何信息,但是编译器可以保证重要的一点:内部一致性,也是我们放进去的是什么类型的对象,取出来还是相同类型的对象,这一点让Java的泛型起码还是有用武之地的。

代码片段四展现就是编译器确保了我们放在t上的类型的确是T(即便它并不知道有关T的任何类型信息)。这种确保其实做了两步工作:

  • set()处的类型检验

  • get()处的类型转换

这两步工作也成为边界动作。

代码片段五

public class Main<T> {

    public List<T> fillList(T t, int size) {
List<T> list = new ArrayList<T>();
for (int i = 0; i < size; i++) {
list.add(t);
}
return list;
} public static void main(String[] args) {
Main<String> m = new Main<String>();
List<String> list = m.fillList("findingsea", 5);
System.out.println(list.toString());
}
} /* Output
[findingsea, findingsea, findingsea, findingsea, findingsea]
*/

代码片段五同样展示的是泛型的内部一致性。

擦除的补偿

如上看到的,但凡是涉及到确切类型信息的操作,在泛型内部都是无法共工作的。那是否有办法绕过这个问题来编程,答案就是显示地传递类型标签。

代码片段六

public class Main<T> {

    public T create(Class<T> type) {
try {
return type.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return null;
} public static void main(String[] args) {
Main<String> m = new Main<String>();
String s = m.create(String.class);
}
}

代码片段六展示了一种用类型标签生成新对象的方法,但是这个办法很脆弱,因为这种办法要求对应的类型必须有默认构造函数,遇到Integer类型的时候就失败了,而且这个错误还不能在编译器捕获。

进阶的方法可以用限制类型的显示工厂和模板方法设计模式来改进这个问题,具体可以参见《Java编程思想 (第4版)》P382。

代码片段七

public class Main<T> {

    public T[] create(Class<T> type) {
return (T[]) Array.newInstance(type, 10);
} public static void main(String[] args) {
Main<String> m = new Main<String>();
String[] strings = m.create(String.class);
}
}

代码片段七展示了对泛型数组的擦除补偿,本质方法还是通过显示地传递类型标签,通过Array.newInstance(type, size)来生成数组,同时也是最为推荐的在泛型内部生成数组的方法。

以上,泛型的第二部分的结束。

Java泛型:类型擦除的更多相关文章

  1. java泛型-类型擦除

    详细内容:参考java编程思想P373,p650. Java 泛型(Generic)的引入加强了参数类型的安全性,减少了类型的转换,但有一点需要注意:Java 的泛型在编译器有效,在运行期被删除,也就 ...

  2. 聊一聊Java泛型的擦除

    最近看了<thinking in java>的第十五章泛型,感觉有些东西需要记录下来. 泛型是Java SE5才被引入的概念,现在我的工作中泛型主要使用在集合,这样可以知道set()和ge ...

  3. Java中泛型 类型擦除

    转自:Java中泛型是类型擦除的 Java 泛型(Generic)的引入加强了参数类型的安全性,减少了类型的转换,但有一点需要注意:Java 的泛型在编译器有效,在运行期被删除,也就是说所有泛型参数类 ...

  4. 【进阶之路】Java的类型擦除式泛型

    Java选择的泛型类型叫做类型擦除式泛型.什么是类型擦除式泛型呢?就是Java语言中的泛型只存在于程序源码之中,在编译后的字节码文件里,则全部泛型都会被替换为原来的原始类型(Raw Type),并且会 ...

  5. 神奇的互换身体术--java的类型擦除

    故事背景 <互换身体>是由环球影业发行的喜剧电影,于2011年8月5日在美国上映.该片由大卫·道金执导,瑞安·雷诺兹.杰森·贝特曼.奥利维亚·王尔德等主演.该片讲述了一位居家好男人和一位蜂 ...

  6. java 泛型 类型作为参量 Class<T> transform

    Class<T> transform T:作为类型,用于定义变量: transform:作为具体类的类:用于创建实例. 类型信息是脱敏的具体类: 可以使用class的具体功能: 不能使用具 ...

  7. 转:有关Java泛型的类型擦除(type erasing)

    转载自:拈花微笑 自从Java 5引入泛型之后,Java与C++对于泛型不同的实现的优劣便一直是饭后的谈资.在我之前的很多training中,当讲到Java泛型时总是会和C++的实现比较,一般得出的结 ...

  8. 关于Java泛型"擦除"的一点思考

    头次写博客,想说的东西不难,关于泛型的疑问,是前一阵在学习jackson中遇到的. 下面就把我所想到的.遇到的,分享出来. 泛型是JDK1.5后的一个特性,是一个参数类型的应用,可以将这个参数声明在类 ...

  9. 初探Java类型擦除

    本篇博客主要介绍了Java类型擦除的定义,详细的介绍了类型擦除在Java中所出现的场景. 1. 什么是类型擦除 为了让你们快速的对类型擦除有一个印象,首先举一个很简单也很经典的例子. // 指定泛型为 ...

  10. 【Java心得总结三】Java泛型上——初识泛型

    一.函数参数与泛型比较 泛型(generics),从字面的意思理解就是泛化的类型,即参数化类型.泛型的作用是什么,这里与函数参数做一个比较: 无参数的函数: public int[] newIntAr ...

随机推荐

  1. 老老实实学习WCF[第二篇] 配置wcf

    老老实实学WCF 第二篇 配置WCF 在上一篇中,我们在一个控制台应用程序中编写了一个简单的WCF服务并承载了它.先回顾一下服务端的代码: using System; using System.Col ...

  2. HP平台由于变量声明冲突导致程序退出时的core

    最近遇到一个莫名的问题,在HP-UX B.11.31 U ia64平台下,程序PetriService在接收到产品化退出或Ctrl-C时,程序在main函数返回后析构全局的CTQueue<SMs ...

  3. Entity Framework Code First 迁移数据库

    新版EF,系统实现过程中如果对Model进行更改,队形修改数据库并不能正常运行项目,需要借助Code First 手动迁移数据库 首先启用迁移,允许迁移Context Tools->Librar ...

  4. javascript之数据推送

    我们使用ajax与后台服务进行交互,常常是通过触发事件来单次交互,但对于有些web应用来说,需要前台与后台保持长连接,前端不定时地接收后台推送的数据信息, 例如:股票行情分析.聊天室和网页在线游戏等. ...

  5. 关于AjaxPro的用法

    1.添加引用AjaxPro.2.dll到项目中 2.添加webconfig iis6添加 在<system.web>中添加: <httpHandlers> <add ve ...

  6. CCNP第四天 OSPF综合实验(1)

    ospf综合实验(1) 本实验主要考察ospf中的接口上的多种工作方式 实验如图所示: 所用拓扑为CCNP标准版,如图: --------------------------------------- ...

  7. sirius的python学习笔记(1)

    1.可以通过try...except语句来简单的判断字符串是否为整数值,如例程 x = raw_input('>') try: print int(x) except ValueError: r ...

  8. 1067. Sort with Swap(0,*) (25)

    时间限制 150 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 CHEN, Yue Given any permutation of the num ...

  9. C# Windows - Button 控件

    .Net Framework提供了一个派生于Control的类System.Windows.Forms.ButtonBase,它实现了Button控件所需的基本功能. System.Windows.F ...

  10. Xcode 向6.0以后版本添加iOS开发空白模板

    打开finder,找到应用程序,找到xcode 右键显示包内容.按照如下目录进行查找:Contents ▸ Developer ▸ Platforms ▸ iPhoneOS.platform ▸ De ...