一、为什么会引入泛型?
泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
- 适用于多种数据类型执行相同的代码
- 泛型中的类型在使用时指定,不需要强制类型转换(类型安全,编译器会检查类型)
二、如何理解Java中的泛型是伪泛型?泛型中类型擦除
Java泛型这个特性是从JDK 1.5才开始加入的,因此为了兼容之前的版本,Java泛型的实现采取了“伪泛型”
的策略,即Java在语法上支持泛型,但是在编译阶段
会进行所谓的“类型擦除”
(Type Erasure),将所有的泛型表示(尖括号中的内容)都替换为具体的类型(其对应的原生态类型),就像完全没有泛型一样。
泛型的类型擦除原则: 消除类型参数声明,即删除<>及其包围的部分。 根据类型参数的上下界推断并替换所有的类型参数为原生态类型:如果类型参数是无限制通配符或没有上下界限定则替换为Object
,如果存在上下界限定则根据子类替换原则取类型参数的最左边限定类型(即父类)。 为了保证类型安全,必要时插入强制类型转换代码。 自动产生“桥接方法”以保证擦除类型后的代码仍然具有泛型的“多态性”。
三、如何证明类型的擦除?
- 原始类型相等
public class Test {
public static void main(String[] args) {
ArrayList<String> list1 = new ArrayList<String>();
list1.add("abc");
ArrayList<Integer> list2 = new ArrayList<Integer>();
list2.add(123);
System.out.println(list1.getClass() == list2.getClass()); // true
}
}
- 通过反射添加其它类型元素
public class Test {
public static void main(String[] args) throws Exception {
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(1); //这样调用 add 方法只能存储整形,因为泛型类型的实例为 Integer
list.getClass().getMethod("add", Object.class).invoke(list, "asd");
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
}
在程序中定义了一个ArrayList泛型类型实例化为Integer对象,如果直接调用add()方法,那么只能存储整数数据,不过当我们利用反射调用add()方法的时候,却可以存储字符串,这说明了Integer泛型实例在编译之后被擦除掉了,只保留了原始类型。
四、什么是类型擦除后的原始类型
原始类型 就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型,无论何时定义一个泛型,相应的原始类型都会被自动提供,类型变量擦除,并使用其限定类型(无限定的变量用Object)替换。
五、如何获取泛型的参数类型?
java.lang.reflect.Type
是Java中所有类型的公共高级接口, 代表了Java中的所有类型. Type体系中类型的包括:数组类型(GenericArrayType
)、参数化类型(ParameterizedType
)、类型变量(TypeVariable
)、通配符类型(WildcardType
)、原始类型(Class
)、基本类型(Class
), 以上这些类型都实现Type
接口。