泛型思想最早在C++语言的模板(Templates)中产生,Java后来也借用了这种思想。虽然思想一致,但是他们存在着本质性的不同。

C++中的模板是真正意义上的泛型,在编译时就将不同模板类型参数编译成对应不同的目标代码,List和List是两种不同的类型,这种泛型被称为真正泛型。

这种泛型实现方式,会导致类型膨胀,因为要为不同具体参数生成不同的类。

Java中List和List虽然在源代码中属于不同的类,但是编译后的字节码中,他们都被替换成原始类型,而两者的原始类型的一样的(List),所以在运行时,List与List就是同一个类。

Java中的泛型是一种特殊的语法,通过类型擦除实现,这种泛型称为伪泛型。

类型擦除,是指将泛型类型实例关联到同一份字节码上。编译器只为泛型类型生成一份字节码,并将其实例关联到这份字节码上。

类型擦除的关键在于从泛型类型中清除类型参数的相关信息,并且再必要的时候添加类型检查和类型转换的方法。

List intList = new ArrayList<>();
intList.add(3);
List strList = new ArrayList<>();
strList.add("Hello");
System.out.println(intList.getClass()== strList.getClass()); //输出结果为true

intList与strList都属于同一个类。

创建一个只能存储Integer的ArrayList对象,在add一个整型数值后,利用反射调用add(Object o)add一个asd字符串,此时运行代码不会报错,运行结果会打印出1和asd两个值。这时再里利用反射调用add(Integer o)方法,运行会抛出codeNoSuchMethodException异常。这充分证明了在编译后,擦除了Integer这个泛型信息,只保留了原始类型。

创建一个List对象intList,利用反射调用其add()方法,向intList中添加一个String类元素,运行代码不会报错。

List intList = new ArrayList<>();
intList.add(3);
intList.getClass().getMethod("add", Object.class).invoke(intList, "Hello");for (int i = 0; i < intList.size(); i++) {
System.out.println(intList.get(i));
}//输出结果为//3//Hello

这说明在编译后,擦出了Integer这个泛型信息,intList为原始类型List。

修改代码getMethod("add", Object.class),改为getMethod("add", Integer.class)
//NoSuchMethodException:
intList.getClass().getMethod("add", Integer.class).invoke(intList, "Hello");

运行报错,intList的类List中没有("add", Integer.class)方法,只有("add", Object.class)。

自动类型转换

Java的泛型除了类型擦除之外,还会自动生成checkcast指令进行强制类型转换。

List intList = new ArrayList<>();
intList.add(3);int a = intList.get(0);

使用intList的get方法返回的是Integer类型的对象。