Java擦除
概述:
Java泛型在使用过程有诸多的问题,如不存在List<String>.class, List<Integer>不能赋值给List<Number>(不可协变),奇怪的ClassCastException等。 正确的使用Java泛型需要深入的了解Java的一些概念,如协变,桥接方法,以及这篇笔记记录的类型擦除。Java泛型的处理几乎都在编译器中进行,编译器生成的bytecode是不包涵泛型信息的,泛型类型信息将在编译处理是被擦除,这个过程即类型擦除。
编译器如何处理泛型:
通常情况下,一个编译器处理泛型有两种方式:
1.Code specialization。在实例化一个泛型类或泛型方法时都产生一份新的目标代码(字节码or二进制代码)。例如,针对一个泛型list,可能需要 针对string,integer,float产生三份目标代码。
2.Code sharing。对每个泛型类只生成唯一的一份目标代码;该泛型类的所有实例都映射到这份目标代码上,在需要的时候执行类型检查和类型转换。
C++中的模板(template)是典型的Code specialization实现。C++编译器会为每一个泛型类实例生成一份执行代码。执行代码中integer list和string list是两种不同的类型。这样会导致代码膨胀(code bloat),不过有经验的C++程序员可以有技巧的避免代码膨胀。
Code specialization另外一个弊端是在引用类型系统中,浪费空间,因为引用类型集合中元素本质上都是一个指针。没必要为每个类型都产生一份执行代码。而这也是Java编译器中采用Code sharing方式处理泛型的主要原因。
Java编译器通过Code sharing方式为每个泛型类型创建唯一的字节码表示,并且将该泛型类型的实例都映射到这个唯一的字节码表示上。将多种泛型类形实例映射到唯一的字节码表示是通过类型擦除(type erasue)实现的。
类型擦除指的是通过类型参数合并,将泛型类型实例关联到同一份字节码上。编译器只为泛型类型生成一份字节码,并将其实例关联到这份字节码上。类型擦除的关键在于从泛型类型中清除类型参数的相关信息,并且再必要的时候添加类型检查和类型转换的方法。
类型擦除可以简单的理解为将泛型java代码转换为普通java代码,只不过编译器更直接点,将泛型java代码直接转换成普通java字节码。
类型擦除的主要过程如下:
1.将所有的泛型参数用其最左边界(最顶级的父类型)类型替换。
2.移除所有的类型参数。
擦除使我们在泛型代码内部,无法获得任何有关参数类型的信息。很蛋疼...
例如:
C++中我们可以这样写:
template<typename T> T imax(T a, T b) {
T copy;
return copy;
}
class A{
};
但是在Java中我们是不能够生成copy的因为们压根就不知道T的类型信息。
那为什么Java要使用擦除呢?
首先能够节省空间避免代码膨胀,主要原因是为了“迁移兼容性”,即允许泛型代码与非泛型代码共存,因为泛型是Java后期才添加的为了兼容以前的代码所以采取了折中的办法。
那么擦除所带来的问题我们如何解决呢?
1: 通过引入类型标签来对擦除进行补偿:
class Building{
@Override
public String toString() {
return "Building ...";
}
}
class House extends Building{
@Override
public String toString() {
return "House ...";
}
}
class TestItem<T>{
Class<T> type; //通过添加类型标签,来获得我要持有的类型的信息
public TestItem(Class<T> type) {
this.type = type;
}
public T getInstance() throws InstantiationException, IllegalAccessException {
T copy = type.newInstance(); //这样我就可以利用类型信息进行必要的处理了
return copy;
}
}
public class Test {
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
TestItem<Building> item = new TestItem<>(Building.class);
System.out.println(item.getInstance());
System.out.println("------------------------");
TestItem<House> item2 = new TestItem<>(House.class);
System.out.println(item2.getInstance());
//出错,因为我们是利用newInstance来创建对象的,就必须保证我们的对象要有默认的构造方法才行,但是Integer没有
// System.out.println("------------------------");
// TestItem<Integer> item3 = new TestItem<>(Integer.class);
// System.out.println(item3.getInstance());
}
}
上面的Integer的问题,我们可以通过传入一个工厂来实现
interface Factory<T>{
T create();
}
class IntegerFactory implements Factory<Integer> {
@Override
public Integer create() {
return new Integer(0);
}
}
class TestItem<T>{
Class<T> type; //通过添加类型标签,来获得我要持有的类型的信息
T copy;
Factory<T> factory;
public <F extends Factory<T>>TestItem(F factory) {
this.factory = factory;
}
public TestItem(Class<T> type) {
this.type = type;
}
public T getInstance() throws InstantiationException, IllegalAccessException {
//copy = type.newInstance(); //这样我就可以利用类型信息进行必要的处理了
copy = factory.create();
return copy;
}
}
public class Test {
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
// TestItem<Building> item = new TestItem<>(Building.class);
// System.out.println(item.getInstance());
// System.out.println("------------------------");
// TestItem<House> item2 = new TestItem<>(House.class);
// System.out.println(item2.getInstance());
//现在把工厂放进去就可以了。
System.out.println("------------------------");
TestItem<Integer> item3 = new TestItem<>(new IntegerFactory());
System.out.println(item3.getInstance());
}
}
2: 同样我们可以通过使用设置擦相互边界来补偿擦除
就像我们在前一篇的比较的时候我们将擦除边界设定成了Comparable,保证了我们的类新信息是可比较的。
http://www.cnblogs.com/E-star/p/3438226.html
注意:
1.虚拟机中没有泛型,只有普通类和普通方法
2.所有泛型类的类型参数在编译时都会被擦除
3.创建泛型对象时请指明类型,让编译器尽早的做参数检查(Effective Java,第23条:请不要在新代码中使用原生态类型)
4.不要忽略编译器的警告信息,那意味着潜在的ClassCastException等着你。
Java擦除的更多相关文章
- Java——擦除
直接代码分析一波: import java.util.*; public class Ex12 { public static void main(String[] args) { Class c1 ...
- 关于Java擦除特性
package thinkingInJava; /* * 模拟擦除 */ public class SimpleHolder { private Object obj ; public void se ...
- Spark案例分析
一.需求:计算网页访问量前三名 import org.apache.spark.rdd.RDD import org.apache.spark.{SparkConf, SparkContext} /* ...
- 【Java心得总结四】Java泛型下——万恶的擦除
一.万恶的擦除 我在自己总结的[Java心得总结三]Java泛型上——初识泛型这篇博文中提到了Java中对泛型擦除的问题,考虑下面代码: import java.util.*; public clas ...
- 记一次由于Java泛型类型擦除而导致的问题,及解决办法
中所周知,Java中的泛型并不像C++.C#一样是真正的泛型,其泛型是通过类型擦除来实现的.具体什么是类型擦除,可以参看这篇博文:http://icyfenix.iteye.com/blog/1021 ...
- java泛型编译时被擦除引起多态的破坏,用 桥方法解决此类问题。(java 桥方法)
在JVM虚拟机中泛型编译的时候,会出现类型擦除.但是,在多态场景中,编译时,擦除方式会出现多态被破坏的可能. 举个栗子: A.java public class A<T> { void g ...
- Java泛型-内部原理: 类型擦除以及类型擦除带来的问题
一:Java泛型的实现方法:类型擦除 大家都知道,Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦掉,正确理解泛型概念的首要前提是理解类型擦除.Java的泛型基本上都是在编译 ...
- Java泛型-类型擦除
一.概述 Java泛型在使用过程有诸多的问题,如不存在List<String>.class, List<Integer>不能赋值给List<Number>(不可协变 ...
- Java类型擦除机制
Java泛型是JDK 5引入的一个特性,它允许我们定义类和接口的时候使用参数类型,泛型在集合框架中被广泛使用.类型擦除是泛型中最让人困惑的部分,本篇文章将阐明什么是类型擦除,以及如何使用它. 一个常见 ...
随机推荐
- POJ2488:A Knight's Journey(dfs)
http://poj.org/problem?id=2488 Description Background The knight is getting bored of seeing the same ...
- edgeR使用学习【转载】
转自:http://yangl.net/2016/09/27/edger_usage/ 1.Quick start 2. 利用edgeR分析RNA-seq鉴别差异表达基因: #加载软件包 librar ...
- java和python中的string和int数据类型的转换
未经允许,禁止转载!!! 在平时写代码的时候经常会用到string和int数据类型的转换 由于java和python在string和int数据类型转换的时候是不一样的 下面总结了java和python ...
- windows 7 中使用命令行创建WiFi热点
就是让你的电脑可以作为WiFi热点,然后供其它支持WiFi的设备上网 首先你的电脑中必须有正常使用的无线网卡 幺幺幺切克闹,开始命令吧,(注:命令是在windows中的命令提示符中运行的) 禁用承载网 ...
- ServiceStack DotNet Core前期准备
下载DotNet Core SDK 下载地址:https://dotnet.microsoft.com/download. 安装完成之后通过cmd的命令行进行确认. 官方自带的cmd比较简陋,可以用c ...
- mysql性能优化2
sql语句优化 性能不理想的系统中除了一部分是因为应用程序的负载确实超过了服务器的实际处理能力外,更多的是因为系统存在大量的SQL语句需要优化. 为了获得稳定的执行性能,SQL语句越简单越好.对复杂的 ...
- Spring 问题总结
Spring问答Top 25:http://www.importnew.com/15851.html [Java面试五]Spring总结以及在面试中的一些问题.:http://www.cnblogs. ...
- bootstrap实战教程
bootstrap实战教程 bootstrap介绍 简介 Bootstrap 是最受欢迎的 HTML.CSS 和 JS 框架,用于开发响应式布局.移动设备优先的 WEB 项目.全球数以百万计的网站都是 ...
- python3.4学习笔记(四) 3.x和2.x的区别,持续更新
python3.4学习笔记(四) 3.x和2.x的区别 在2.x中:print html,3.x中必须改成:print(html) import urllib2ImportError: No modu ...
- LWIP使用经验---变态级(转)
源:LWIP使用经验---变态级 LWIP使用经验 一 LWIP内存管理 数据包管理 设置内存大小 宏编译开关 二 LWIP启动时序 三 LWIP运行逻辑 接收数据包 SequentialAPI函数调 ...