什么是泛型?
Java 泛型(generics)是JDK5中引入的一种参数化类型特性。
为什么使用泛型,使用泛型的好处?
- 代码更健壮(只要编译期没有警告,那么运行期就不会出现
ClassCastException
) - 代码更简洁(不用强转)
- 代码更灵活,复用
什么是参数化类型:
- 把类型当参数一样传递
- <数据类型> 只能是引用类型(泛型的副作用)
举个例子:
public interface Plate<T> {public void set(T t);public T get();
}
public class ATPlate<T> implements Plate<T> {private List<T> items = new ArrayList<T>(10);public <T> ATPlate<T> getATPlate() {return new ATPlate<T>();}
}
Plate<T>
中的 "T
” 称为类型参数,Plate<T>
整个称为泛型类型Plate<Banana>
中的 “Banana
” 称为实际类型参数,Plate<Banana>
整个称为参数化的类型ParameterizedType
泛型其实就是在类上面传参,可以类比方法传参,只不过类上面传的参数类型也只能是类。
泛型类型可以通过extends
指定多个限定类型。
什么是多个限定类型,例如:
class A {}
interface B {}
interface C {}// 具有多个限定的类型变量是范围中列出的所有类型的子类型。如果范围之一是类,则必须首先声明类
class D<T extends B & A & C> {} // 编译报错
class D<T extends A & B & C> {} // 编译OK
泛型擦除
Q:Java 泛型的原理?什么是泛型擦除机制?
- Java 的泛型是 JDK5 引入的特性,为了向下兼容,虚拟机其实是不支持泛型,所以 Java 实现的是一种伪泛型机制,也就是说 Java 在编译期擦除了所有的泛型信息,这样 Java 就不需要产生新的类型到字节码,所有的泛型类型最终都是一种原始类型,在 Java 运行时根本就不存在泛型信息。
Q:Java 编译器具体是如何擦除泛型的?
- 检查泛型类型,获取目标类型
- 擦除类型变量,并替换为限定类型
- 如果泛型类型的类型变量没有限定(
<T>
),则用Object
作为原始类型 - 如果有限定(
<T extends XCIass>
),则用XCIass
作为原始类型 - 如果有多个限定(
T extends XCIass1 & XCIass2
),则使用第一个边界XCIass1
作为原始类型
- 如果泛型类型的类型变量没有限定(
- 在必要时插入类型转换以保持类型安全
- 生成桥方法以在扩展时保持多态性
例如,只有一个泛型 T
擦除后只有一个 Object
对象:
如果泛型有多个继承限定类型,则使用第一个限定类作为擦除后的类型,此外如果泛型类继承了支持泛型的接口,还会生成桥方法:
如何通过反射获取泛型参数
泛型虽然被擦除了,但是在 类常量池 里面其实保留了泛型信息,所以可以通过反射获取泛型的信息 getGenericType()
Java 的泛型擦除并不是将所有泛型信息全部都擦除了,会将类上和方法上声明的泛型信息保存在字节码中的 Signature
属性中,这也是反射能够获取泛型的原因。但是在方法中的泛型信息是完全擦除了。
public class HelloWorld {Map<String, String> map;public static void main(String[] args){ try {Field field = HelloWorld.class.getDeclaredField("map"); Type type = field.getGenericType();System.out.println(type);// java.util.Map<java.lang.String, java.lang.String>System.out.println(type instanceof ParameterizedType); // trueParameterizedType pType = (ParameterizedType) type;System.out.print1n(pType.getRawType());// interface java.util.Map for (Type argType : pType.getActualTypeArguments()) {System.out.println(argType); // class java.lang.String }System.out.println(pType.getOwnerType()); // nullMethod method =HelloWorld.class.getMethod("applyMethod",Map.Entry.class);