java泛型与类型擦除_Java 泛型和类型擦除

一、概念

在 Java 语言处于还没有出现泛型的版本时,只能通过 Object 是所有类型的父类和类型强制转换两个特点的配合来实现类型泛化。这样做有个缺点,就是只有程序员和运行期的虚拟机才知道这个 Object 到底是个什么类型的对象。在编译期,编译器无法检查这个 Object 的强制转换是否成功。因此,许多 ClassCastException 的风险就会转移到程序运行期之中。

泛型是 JDK 1.5 的一项新增特性,它的本质是参数化类型(Parametersized Type),就比如我们定义方法的时候,定义一个变量,称为形参,变量值根据传进去的实参的值不同而改变。而泛型的出现,就是为了解决类型也能根据传进去的类型改变的问题,也就是说所操作的数据类型被指定为一个参数。主要用在定义类、接口、方法的创建上,可以很好的减少代码的重复。

然而美中不足的是,Java 中的泛型实现并不是真正的泛型,它只在程序源码中存在,在编译后的字节码文件中,就已经替换为原来的原生类型了,并在相应的地方插入了强制转换代码,因此对于运行期的 Java 语言来说,ArrayList 与 ArrayList 就是同一个类,这种实现方法被称为泛型的类型擦除,所以泛型技术实际上是 Java 语言的一颗语法糖。

tips:语法糖虽然不会提供实际性的功能改进,但是他们或能提高工作效率,或能提升语法的严谨性,或能减少编码出错的机会。常见的语法糖还有“内部类”、“自动装箱/拆箱”、“断言语句”、“枚举类”等等

二、泛型使用

1. 泛型中的标识符含义

E - Element (在集合中使用,因为集合中存放的是元素)

T - Type(Java 类)

K - Key(键)

V - Value(值)

N - Number(数值类型)

? -  表示不确定的java类型

S、U、V  - 2nd、3rd、4th types

2、定义一个泛型方法

首先,泛型的声明,必须在方法的修饰符(public, static, final, abstract 等)之后,返回值声明之前,可以声明多个泛型,用逗号隔开。

public static T1 print(List list, Listlist2) {return list.get(0);

}

3、定义一个泛型类

@Datapublic class Box {//这里可以定义多个泛型,用逗号分割

privateString name;privateT t;/*** 泛型继承*/

public static class CircleBox extends Box{

}public static class SquareBox extends Box{

}

}

三、泛型擦除

接下来我们来看一个泛型擦除的例子

public static voidmain(String[] args) {

HashMap map = new HashMap<>();

map.put("hello", "hello");

map.put("world", "world");

System.out.println(map.get("hello"));

System.out.println(map.get("world"));

}

把这段 Java 代码编译成 Class 文件,然后再用字节码反编译工具进行反编译后,会发现所有泛型都不见了,泛型类型都变回了原生类型,只是在相应的地方做了类型强制转换。

public static voidmain(String[] args) {

HashMap map= newHashMap();

map.put("hello", "hello");

map.put("world", "world");

System.out.println((String)map.get("hello"));

System.out.println((String)map.get("world"));

}

四、泛型识别

按照我们上面的说法,泛型在编译期间就被擦除了,那么在代码运行期各种场景(如反射等)下该如何识别参数化类型呢?总不能识别出来都是 Object 吧?这就要说到一个虚拟机属性 —— Signature,它的作用就是存储一个方法在字节码层面的特征签名,这个属性保存的参数类型并不是原生类型,而是包括了参数化类型的信息。

因此,擦除法所谓的擦除,仅仅是对方法的 Code 属性中的字节码进行擦除,实际上元数据中还是保留了泛型信息,这也是我们能通过反射等手段取得参数化类型的根本依据。

你可能感兴趣的:(java泛型与类型擦除)