实际上,我们知道在Java中,是不存在类似List
,List
等等这样的类型,真正被加载进方法区存储的只有List类型,这就是类型擦除。
Java在1.5版本才推出泛型这个概念,当时Java语言的用户群已经是一个相当庞大的数量了,所以向前兼容也是当时Java开发者着重考虑的一个点。不管在前泛型时代还是泛型时代,以下的写法都是被允许的,它们的泛型元素都是Raw类型:
List list;
ArrayList array;
基本类型无法作为泛型实参,在使用过程中就意味着会有装箱和拆箱的开销:
List<int> intArray; // compile error
List<Integer> intArray; // compile success
List<double> intArray; // compile error
List<Double> intArray; // compile success
为此,谷歌也推出了SpareArray
的数据结构来提高执行效率,如使用SparseBooleanArray
来取代HashMap
,SparseIntArray
用来取代HashMap
等等,大家有兴趣的可以研究。
类型擦除意味着List
和List
编译后其类型都是List
,也就是属于同个方法:
public void testMethod(List<Integer> array) {}
public void testMethod(List<Double> array) {} // compile error
由于类型擦除后像List
这样的类型是不存在的,所以也就无法直接当成真实类型使用:
static <T> void genericMethod(T t) {
T newInstance = new T(); // compile errror
Class c = T.class; // compile errror
List<T> list = new ArrayList<T>(); // compile errror
if (list instance List<Integer>) {} // compile errror
}
这也是Gson.fromJson需要传入Class的原因:
public <T> T fromJson(String json, Class<T> classOfT)
throws JsonSyntaxException {
Object object = fromJson(json, (Type)classOfT);
return Primitives.wrap(classOfT).cast(object);
}
类的泛型参数只有在类实例化的时候才知道,而静态方法的执行不需要有类的示例存在,所以静态方法无法引用类泛型参数:
class GenericClass<T> {
public static T max(T a, T b) {}
}
在实际项目中我们会经常看见这样的代码:
List<String> strList = new Array<>();
strList.add("Hallo");
String value = strList.get(0);
但实际字节码指令执行strList.get()
方法时,经过类型擦除后,还是需要做类型强转:
INVOKEINTERFACE java/util/List.get (I)Ljava/lang/Object;
CHECKCAST java/lang/String
在Java中,泛型类型虽然被擦除了,但是被擦除的类型信息还是会以某种形式存储下来,并支持在运行时获取。这种形式就是指元素附加的签名信息(Signatures),谷歌是这么定义Signatures:
Signatures encode declarations written in Java programming language that use types outside the type system of the Java Virtual Machine. They support reflection and debugging, as well as compilation when only class files are available.
获取Signatures的方法如下:
class GenericClass<T> {}
class ConcreteClass extends GenericClass<String> {
public List<String> getArray() {}
}
// 获取类元素泛型
ParameterizedType genericType =
(ParameterizedType)ConcreteClass.class.getGenericSuperClass();
// 获取方法元素泛型
ParameterizedType genericType =
(ParameterizedType)ConcreteClass.class.getMethod("getArray").getGenericReturnTypes、();
当然,在混淆时需要保留签名信息:
-keepattributes Signature
一个很经典的例子,Gson构建泛型Type,实际上调用的就是getGenericReturnTypes
方法:
Type genericType = new TypeToken<List<Integer>>(){}.getType();
总结起来就是,Java的泛型实现正是使用类型擦除机制,而对于类型擦除机制,有其优势也有其弊端,在编程开发中需要有对其详细的见解才能写出高效的代码。