本篇博客主要介绍了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. 写在最后

如果各位发现文章中有问题的,欢迎大家不吝赐教,我会及时的更正。

参考:

  1. Java语言类型擦除
  2. 下界通配符
  3. List<?>和List的区别

往期文章:

相关:

  • 个人网站: Lunhao Hu
  • 微信公众号: SH的全栈笔记(或直接在添加公众号界面搜索微信号LunhaoHu)

初探Java类型擦除的更多相关文章

  1. Java类型擦除机制

    Java泛型是JDK 5引入的一个特性,它允许我们定义类和接口的时候使用参数类型,泛型在集合框架中被广泛使用.类型擦除是泛型中最让人困惑的部分,本篇文章将阐明什么是类型擦除,以及如何使用它. 一个常见 ...

  2. JAVA类型擦除

    Java泛型-类型擦除 一.概述 Java泛型在使用过程有诸多的问题,如不存在List<String>.class, List<Integer>不能赋值给List<Num ...

  3. Java泛型-内部原理: 类型擦除以及类型擦除带来的问题

    一:Java泛型的实现方法:类型擦除 大家都知道,Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦掉,正确理解泛型概念的首要前提是理解类型擦除.Java的泛型基本上都是在编译 ...

  4. Java泛型-类型擦除

    一.概述 Java泛型在使用过程有诸多的问题,如不存在List<String>.class, List<Integer>不能赋值给List<Number>(不可协变 ...

  5. [改善Java代码]Java的泛型是类型擦除的

    泛型可以减少强制类型的转换,可规范集合的元素类型,还可以提高代码的安全性和可读性,正是因为有了这些优点,自从Java引入泛型之后,项目的编码规则上便多了一条,优先使用泛型. Java泛型(Generi ...

  6. Java泛型:类型擦除

    类型擦除 代码片段一 Class c1 = new ArrayList<Integer>().getClass(); Class c2 = new ArrayList<String& ...

  7. Java泛型类与类型擦除

    转载自:http://blog.csdn.net/lonelyroamer/article/details/7868820 一.Java泛型的实现方法:类型擦除 前面已经说了,Java的泛型是伪泛型. ...

  8. java泛型 8 泛型的内部原理:类型擦除以及类型擦除带来的问题

    参考:java核心技术 一.Java泛型的实现方法:类型擦除 前面已经说了,Java的泛型是伪泛型.为什么说Java的泛型是伪泛型呢?因为,在编译期间,所有的泛型信息都会被擦除掉.正确理解泛型概念的首 ...

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

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

随机推荐

  1. Dsamain

    TechNet 库 Windows Server Windows Server 2008 R2 und Windows Server 2008 Windows Server 命令.参考和工具 Comm ...

  2. 5、CSS基础part-3

    1.CSS列表 ①类型 ul.disc {list-style-type: disc} ②位置 ul.inside {list-style-position: inside} ③列表图像 2.表格

  3. jmeter全局变量配置:将token运用到全局(跨线程组使用变量)

    请注意元器件的执行顺序: 请将提取token的配置原件放在设置全局变量的配置元器件前面(本来是一个超级马虎的人,真是俗称“方脑壳”啊) 1.获取登录后的token(提取可以用json path Ext ...

  4. 项目实战:CRM客户关系管理系统开发

    21-CRM第一节内容概要 21.1 Stark组件介绍:实现基本的增删改查+自定义复杂操作: 21.2 单例模式:最简单的单例模式: 21.3 路由系统(分发): 21.4 制作启动文件-Djang ...

  5. python 学习分享-实战篇选课系统

    # 角色:学校.学员.课程.讲师 # 要求: # 1. 创建北京.上海 2 所学校 # 2. 创建linux , python , go 3个课程 , linux\py 在北京开, go 在上海开 # ...

  6. postgresql connection failure:SQLSTATE[08006] [7] could not connect to server: Permission denied Is the server running on host "127.0.0.1" and accepting TCP/IP connections on port 5432?

    PHP 程序无法连接到 CentOS 上的PostgreSQL,但是在 CentOS 服务器上却能正常运行 psql, 操作如下:多次重启 PG 数据库后发现 CGI 脚本无法连接数据库,但是可以使用 ...

  7. CSU-2049 象棋

    CSU-2049 象棋 Description 車是中国象棋中的一种棋子,它能攻击同一行或同一列中没有其他棋子阻隔的棋子.一天,小度在棋盘上摆起了许多車--他想知道,在一共N×M个点的矩形棋盘中摆最多 ...

  8. JDBC 学习笔记(十一)—— JDBC 的事务支持

    1. 事务 在关系型数据库中,有一个很重要的概念,叫做事务(Transaction).它具有 ACID 四个特性: A(Atomicity):原子性,一个事务是一个不可分割的工作单位,事务中包括的诸操 ...

  9. 习题:八数码难题(双向BFS)

    八数码难题(wikioi1225) [题目描述] 在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字.棋盘中留有一个空格,空格用0来表示.空格周围的棋子可以移到空格中.要求解的问题是:给出 ...

  10. Pointcut is not well-formed: expecting 'name pattern' at character position 53

    报错内容: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataso ...