初探Java类型擦除
本篇博客主要介绍了Java类型擦除的定义,详细的介绍了类型擦除在Java中所出现的场景。
1. 什么是类型擦除
为了让你们快速的对类型擦除有一个印象,首先举一个很简单也很经典的例子。
// 指定泛型为String
List<String> list1 = new ArrayList<>();
// 指定泛型为Integer
List<Integer> list2 = new ArrayList<>();
System.out.println(list1.getClass() == list2.getClass()); // true
上面的判断结果是true。代表了两个传入了不同泛型的List最终都编译成了ArrayList,成为了同一种类型,原来的泛型参数String和Integer被擦除掉了。这就是类型擦除的一个典型的例子。
而如果我们说到类型擦除为什么会出现,我们就必须要了解泛型。
2. 泛型
2.1. 泛型的定义
随着2004年9月30日,工程代号为Tiger的JDK 1.5发布,泛型从此与大家见面。JDK 1.5在Java语法的易用性上作出了非常大的改进。除了泛型,同版本加入的还有自动装箱、动态注解、枚举、可变长参数、foreach循环等等。
而在1.5之前的版本中,为了让Java的类具有通用性,参数类型和返回类型通常都设置为Object,可见,如果需要不用的类型,就需要在相应的地方,对其进行强制转换,程序才可以正常运行,十分麻烦,稍不注意就会出错。
泛型的本质就是参数化类型。也就是,将一个数据类型指定为参数。引入泛型有什么好处呢?
泛型可以将JDK 1.5之前在运行时才能发现的错误,提前到编译期。也就是说,泛型提供了编译时类型安全的检测机制。例如,一个变量本来是Integer类型,我们在代码中设置成了String,没有使用泛型的时候只有在代码运行到这了,才会报错。
而引入泛型之后就不会出现这个问题。这是因为通过泛型可以知道该参数的规定类型,然后在编译时,判断其类型是否符合规定类型。
泛型总共有三种使用方法,分别使用于类、方法和接口。
3. 泛型的使用方法
3.1 泛型类
3.1.1 定义泛型类
简单的泛型类可以定义为如下。
public class Generic<T> {
T data;
public Generic(T data) {
setData(data);
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
其中的T代表参数类型,代表任何类型。当然,并不是一定要写成T,这只是大家约定俗成的习惯而已。有了上述的泛型类之后我们就可以像如下的方式使用了。
3.1.2 使用泛型类
// 假设有这样一个具体的类
public class Hello {
private Integer id;
private String name;
private Integer age;
private String email;
}
// 使用泛型类
Hello hello = new Hello();
Generic<Hello> result = new Generic<>();
resule.setData(hello);
// 通过泛型类获取数据
Hello data = result.getData();
当然如果泛型类不传入指定的类型的话,泛型类中的方法或者成员变量定义的类型可以为任意类型,如果打印result.getClass()的话,会得到Generic。
3.2. 泛型方法
3.2.1 定义泛型方法
首先我们看一下不带返回值的泛型方法,可以定义为如下结构。
// 定义不带返回值的泛型方法
public <T> void genericMethod(T field) {
System.out.println(field.getClass().toString());
}
// 定义带返回值的泛型方法
private <T> T genericWithReturnMethod(T field) {
System.out.println(field.getClass().toString());
return field;
}
3.2.2 调用泛型方法
// 调用不带返回值泛型方法
genericMethod("This is string"); // class java.lang.String
genericMethod(56L); // class java.lang.Long
// 调用带返回值的泛型方法
String test = genericWithReturnMethod("TEST"); // TEST class java.lang.String
带返回值的方法中,T就是当前函数的返回类型。
3.3. 泛型接口
泛型接口定义如下
public interface genericInterface<T> {
}
使用的方法与泛型类类似,这里就不再赘述。
4. 泛型通配符
什么是泛型通配符?官方一点的解释是
Type of unknown.
也就是无限定的通配符,可以代表任意类型。用法也有三种,<?>,<? extends T>和<? super T>。
既然已经有了T这样的代表任意类型的通配符,为什么还需要这样一个无限定的通配符呢?是因为其主要解决的问题是泛型继承带来的问题。
4.1. 泛型的继承问题
首先来看一个例子
List<Integer> integerList = new ArrayList<>();
List<Number> numberList = integerList;
我们知道,Integer是继承自Number类的。
public final class Integer extends Number implements Comparable {
....
}
那么上述的代码能够通过编译吗?肯定是不行的。Integer继承自Number不代表List 和 List之间有继承关系。那通配符的应用场景是什么呢?
4.2. 通配符的应用场景
在其他函数中,例如JavaScript中,一个函数的参数可以是任意的类型,而不需要进行任意的类型转换,所以这样的函数在某些应用场景下,就会具有很强的通用性。
而在Java这种强类型语言中,一个函数的参数类型是固定不变的。那如果想要在Java中实现类似于JavaScript那样的通用函数该怎么办呢?这也就是为什么我们需要泛型的通配符。
假设我们有很多动物的类, 例如Dog, Pig和Cat三个类,我们需要有一个通用的函数来计算动物列表中的所有动物的腿的总数,如果在Java中,要怎么做呢?
可能会有人说,用泛型啊,泛型不就是解决这个问题的吗?泛型必须指定一个特定的类型。正式因为泛型解决不了...才提出了泛型的通配符。
4.3. 无界通配符
无界通配符就是?。看到这你可能会问,这不是跟T一样吗?为啥还要搞个?。他们主要区别在于,T主要用于声明一个泛型类或者方法,?主要用于使用泛型类和泛型方法。下面举个简单的例子。
// 定义打印任何类型列表的函数
public static void printList(List<?> list) {
for (Object elem: list) {
System.out.print(elem + " ");
}
}
// 调用上述函数
List<Integer> intList = Arrays.asList(1, 2, 3);
List<String> stringList = Arrays.asList("one", "two", "three");
printList(li);// 1 2 3
printList(ls);// one two three
上述函数的目的是打印任何类型的列表。可以看到在函数内部,并没有关心List中的泛型到底是什么类型的,你可以将<?>理解为只提供了一个只读的功能,它去除了增加具体元素的能力,只保留与具体类型无关的功能。从上述的例子可以看出,它只关心元素的数量以及其是否为空,除此之外不关心任何事。
再反观T,上面我们也列举了如何定义泛型的方法以及如果调用泛型方法。泛型方法内部是要去关心具体类型的,而不仅仅是数量和不为空这么简单。
4.4. 上界通配符<? extends T>
既然?可以代表任何类型,那么extends又是干嘛的呢?
假设有这样一个需求,我们只允许某一些特定的类型可以调用我们的函数(例如,所有的Animal类以及其派生类),但是目前使用?,所有的类型都可以调用函数,无法满足我们的需求。
private int countLength(List< ? extends Animal> list) {...}
使用了上界通配符来完成这个公共函数之后,就可以使用如下的方式来调用它了。
List<Pig> pigs = new ArrayList<>();
List<Dog> dogs = new ArrayList<>();
List<Cat> cats = new ArrayList<>();
// 假装写入了数据
int sum = 0;
sum += countLength(pigs);
sum += countLength(dogs);
sum += countLength(cats);
看完了例子,我们就可以简单的得出一个结论。上界通配符就是一个可以处理任何特定类型以及是该特定类型的派生类的通配符。
可能会有人看的有点懵逼,我结合上面的例子,再简单的用人话解释一下:上界通配符就是一个啥动物都能放的盒子。
4.5. 下界通配符<? super Animal>
上面我们聊了上界通配符,它将未知的类型限制为特定类型或者该特定的类型的子类型(也就是上面讨论过的动物以及一切动物的子类)。而下界通配符则将未知的类型限制为特定类型或者该特定的类型的超类型,也就是超类或者基类。
在上述的上界通配符中,我们举了一个例子。写了一个可以处理任何动物类以及是动物类的派生类的函数。而现在我们要写一个函数,用来处理任何是Integer以及是Integer的超类的函数。
public static void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 10; i++) {
list.add(i);
}
}
5. 类型擦除
简单的了解了泛型的几种简单的使用方法之后,我们回到本篇博客的主题上来——类型擦除。泛型虽然有上述所列出的一些好处,但是泛型的生命周期只限于编译阶段。
本文最开始的给出的样例就是一个典型的例子。在经过编译之后会采取去泛型化的措施,编译的过程中,在检测了泛型的结果之后会将泛型的相关信息进行擦除操作。就像文章最开始提到的例子一样,我们使用上面定义好的Generic泛型类来举个简单的例子。
Generic<String> generic = new Generic<>("Hello");
Field[] fs = generic.getClass().getDeclaredFields();
for (Field f : fs) {
System.out.println("type: " + f.getType().getName()); // type: java.lang.Object
}
getDeclaredFields是反射中的方法,可以获取当前类已经声明的各种字段,包括public,protected以及private。
可以看到我们传入的泛型String已经被擦除了,取而代之的是Object。那之前的String和Integer的泛型信息去哪儿了呢?可能这个时候你会灵光一闪,那是不是所有的泛型在被擦除之后都会变成Object呢?别着急,继续往下看。
当我们在泛型上面使用了上界通配符以后,会有什么情况发生呢?我们将Generic类改成如下形式。
public class Generic<T extends String> {
T data;
public Generic(T data) {
setData(data);
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
然后再次使用反射来查看泛型擦除之后类型。这次控制台会输出type: java.lang.String。可以看到,如果我们给泛型类制定了上限,泛型擦除之后就会被替换成类型的上限。而如果没有指定,就会统一的被替换成Object。相应的,泛型类中定义的方法的类型也是如此。
6. 写在最后
如果各位发现文章中有问题的,欢迎大家不吝赐教,我会及时的更正。
参考:
往期文章:
相关:
- 个人网站: Lunhao Hu
- 微信公众号: SH的全栈笔记(或直接在添加公众号界面搜索微信号LunhaoHu)
初探Java类型擦除的更多相关文章
- Java类型擦除机制
Java泛型是JDK 5引入的一个特性,它允许我们定义类和接口的时候使用参数类型,泛型在集合框架中被广泛使用.类型擦除是泛型中最让人困惑的部分,本篇文章将阐明什么是类型擦除,以及如何使用它. 一个常见 ...
- JAVA类型擦除
Java泛型-类型擦除 一.概述 Java泛型在使用过程有诸多的问题,如不存在List<String>.class, List<Integer>不能赋值给List<Num ...
- Java泛型-内部原理: 类型擦除以及类型擦除带来的问题
一:Java泛型的实现方法:类型擦除 大家都知道,Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦掉,正确理解泛型概念的首要前提是理解类型擦除.Java的泛型基本上都是在编译 ...
- Java泛型-类型擦除
一.概述 Java泛型在使用过程有诸多的问题,如不存在List<String>.class, List<Integer>不能赋值给List<Number>(不可协变 ...
- [改善Java代码]Java的泛型是类型擦除的
泛型可以减少强制类型的转换,可规范集合的元素类型,还可以提高代码的安全性和可读性,正是因为有了这些优点,自从Java引入泛型之后,项目的编码规则上便多了一条,优先使用泛型. Java泛型(Generi ...
- Java泛型:类型擦除
类型擦除 代码片段一 Class c1 = new ArrayList<Integer>().getClass(); Class c2 = new ArrayList<String& ...
- Java泛型类与类型擦除
转载自:http://blog.csdn.net/lonelyroamer/article/details/7868820 一.Java泛型的实现方法:类型擦除 前面已经说了,Java的泛型是伪泛型. ...
- java泛型 8 泛型的内部原理:类型擦除以及类型擦除带来的问题
参考:java核心技术 一.Java泛型的实现方法:类型擦除 前面已经说了,Java的泛型是伪泛型.为什么说Java的泛型是伪泛型呢?因为,在编译期间,所有的泛型信息都会被擦除掉.正确理解泛型概念的首 ...
- 转:有关Java泛型的类型擦除(type erasing)
转载自:拈花微笑 自从Java 5引入泛型之后,Java与C++对于泛型不同的实现的优劣便一直是饭后的谈资.在我之前的很多training中,当讲到Java泛型时总是会和C++的实现比较,一般得出的结 ...
随机推荐
- 组装需要的json数据格式
在实际项目中有时候会遇到一些有特殊要求的控件,比如easyui-combogrid,加载的并不是常见的json格式,这里我遇到过需要加载类似省市县这种三级数据格式.最后也是从别人的博客中学到的如何组装 ...
- 使用JavaConfig方式-Spring 基础学习
在Spring的使用中,大量采用xml方式配置类之间的关系太过于繁琐(个人这么认为),而在学习了Spring4后发下使用JavaConfig方式来配置这些关系会更加的简单明了. 测试环境 1. Apa ...
- ogre3D学习基础14 -- 雾化效果与天空面,天空盒,天空穹
前几天设置天空盒时一直出问题,现在问题终于解决了,问题来的莫名其妙,走的也莫名其妙. 第一,还是框架,我们依然使用ExampleApplication文件,框架如下 #include "Ex ...
- c++ primer 6 练习题 (非复习题)
第7章 7.13-1调和平均数 //7.13-1 excise.cpp 调和平均数 #include <iostream> double calculate(double a,double ...
- [oldboy-django][6其他]rest framwork有关事
官网地址: https://github.com/encode/django-rest-framework 英文教程:http://www.django-rest-framework.org/tuto ...
- hdu 3714 Error Curves(三分)
Error Curves Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others) Tot ...
- JSP与JavaBeans
JavaBeans简介 JavaBeans是一种符合一定标准的普通java类,需要满足下面几点: 1 类是public 2 属性私有 3 空的public构造方法 4 通过getter setter操 ...
- 【bzoj3435】[Wc2014]紫荆花之恋 替罪点分树套SBT
题目描述 强强和萌萌是一对好朋友.有一天他们在外面闲逛,突然看到前方有一棵紫荆树.这已经是紫荆花飞舞的季节了,无数的花瓣以肉眼可见的速度从紫荆树上长了出来.仔细看看的话,这个大树实际上是一个带权树.每 ...
- 【bzoj2096】[Poi2010]Pilots 双指针法+STL-set
题目描述 Tz又耍畸形了!!他要当飞行员,他拿到了一个飞行员测试难度序列,他设定了一个难度差的最大值,在序列中他想找到一个最长的子串,任意两个难度差不会超过他设定的最大值.耍畸形一个人是不行的,于是他 ...
- css对html中表格单元格td文本过长的处理
参考 http://www.cnblogs.com/lekko/archive/2013/04/30/3051638.html http://www.zhangxinxu.com/wordpress/ ...
