共计 2595 个字符,预计需要花费 7 分钟才能阅读完成。
说到 Java 泛型,面试官其实就是想要问你是否了解过 Java 中的类型擦除。当然,如果你面临的是校招面试,能说出类型擦除已经很不错,但是我个人觉得,如果面的是中级 Android 程序员甚至高级 Android 程序员,对类型擦除的各种特性以及优势还是需要有比较深的了解。所以,回答这个问题,先从类型擦除入手:
1. 类型擦除有什么优势?
1.1 减小运行时内存负担
实际上,我们知道在 Java 中,是不存在类似 List<Integer>
,List<Double>
等等这样的类型,真正被加载进方法区存储的只有 List 类型,这就是类型擦除。
1.2 向前兼容性好
Java 在 1.5 版本才推出泛型这个概念,当时 Java 语言的用户群已经是一个相当庞大的数量了,所以向前兼容也是当时 Java 开发者着重考虑的一个点。不管在前泛型时代还是泛型时代,以下的写法都是被允许的,它们的泛型元素都是 Raw 类型:
List list;
ArrayList array;
2. 类型擦除存在什么问题?
2.1 基本类型无法作为泛型实参
基本类型无法作为泛型实参,在使用过程中就意味着会有装箱和拆箱的开销:
List<int> intArray; // compile error
List<Integer> intArray; // compile success
List<double> intArray; // compile error
List<Double> intArray; // compile success
为此,谷歌也推出了 SpareArray
的数据结构来提高执行效率,如使用 SparseBooleanArray
来取代 HashMap<Integer, Boolean>
,SparseIntArray
用来取代 HashMap<Integer, Integer>
等等,大家有兴趣的可以研究。
2.2 泛型类型无法用作方法重载
类型擦除意味着 List<Integer>
和List<Double>
编译后其类型都是List
,也就是属于同个方法:
public void testMethod(List<Integer> array) {}
public void testMethod(List<Double> array) {} // compile error
2.3 泛型类型无法当做真实类型使用
由于类型擦除后像 List<T>
这样的类型是不存在的,所以也就无法直接当成真实类型使用:
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);
}
2.4 静态方法无法引用类泛型参数
类的泛型参数只有在类实例化的时候才知道,而静态方法的执行不需要有类的示例存在,所以静态方法无法引用类泛型参数:
class GenericClass<T> {public static T max(T a, T b) {}}
2.5 泛型类型会带来类型强转的运行时开销
在实际项目中我们会经常看见这样的代码:
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
3. 类型擦除后怎么获取泛型参数?
在 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 的泛型实现正是使用类型擦除机制,而对于类型擦除机制,有其优势也有其弊端,在编程开发中需要有对其详细的见解才能写出高效的代码。