Java与Kotlin中的泛型之:擦除、不变、协变、逆变
前言
- 对于Java中泛型的使用方法和应用场景等,不在本文章中作讨论,在阅读此篇文章时,我已经默认你对Java泛型有了一个较为清楚的认识和较为熟悉的应用熟练度。
- 代码中的部分声明因篇幅原因没办法完全展示,只展示关键代码,但是别担心,你一定能看懂。
- 本文章的内容均参考《Kotlin核心编程》中对该知识点的讲述,以及结合本人的实际开发经验。
概述
Java中的泛型(Generics)是Java 5中引入的一个特性,它提供了一种编译时类型安全检测机制,该机制允许程序员在类、接口、方法中使用类型参数(type parameters)。这样,类、接口和方法就可以操作多种类型的数据,而不是在编译时就已经确定下来的某一种类型。泛型的使用极大地增强了Java代码的复用性、灵活性和安全性。
尤其是JDK源码中在Java集合框架中大量使用到泛型,例如我们较为常用的List接口声明:
public interface List<E> extends Collection<E>
一道Java面试题
请问:以下输出结果是什么?
List<String> list1 = new ArrayList<String>();
List<Integer> list2 = new ArrayList<Integer>();
System.out.println(list1.getClass() == list2.getClass());
你可能会认为:List<String>
和List<Integer>
很明显不是同一个类型,一定是返回false
。
但运行之后你会发现,返回结果为:true
。
在这其中作妖的就是接下来要说的:泛型的类型擦除
泛型的背后:类型擦除(Type Erasure)
概念
类型擦除是指:通过一些技术手段去掉(或”擦除“)编程语言中的类型信息,使得在运行时不再依赖于这些类型信息。这通常涉及到在编译器对类型信息进行替换或处理,以便在运行时能够以一种统一的方式处理不同类型的对象。
简单来说就是:编译器会对类型进行擦除掩盖,使得程序在运行时无法得知泛型具体的类型信息。
Java中的Array与List
思考一个问题,你是否在Java中写过如下代码:
Array<SomeType> array = new Array<>();
可能你会疑惑,数组的定义不是以下这种形式吗:
SomeType[] array = new SomeType[10];
是的,我们无法像定义一个List
一样,使用泛型定义Array
中的类型,这涉及到一个关键点:
Java无法声明一个泛型数组。
Java为什么无法声明一个泛型数组?
在解释这个问题之前,我们先来看一下Java对于泛型的实现方式:
Dog[] dogArray = new Dog[10];
List<Dog> dogList = new ArrayList<>();
System.out.println(dogArray.getClass());
System.out.println(dogList.getClass());
// 运行结果
class [LDog;
class java.util.ArrayList
从上面的运行结果我们可以得知,Java中的Array
在运行时是可以获取到自身类型的,而List<Dog>
在运行时只知道自己是一个List
,而无法获取泛型参数的类型。
这个现象与Java底层对泛型的实现有关:Java为了向后兼容所作出的牺牲。这需要在原本不支持泛型的基础上,以不改变老版本代码为目的,在新版本中以一种比较别扭的方式实现泛型———类型擦除。
因此我们可以下一个结论:Java中的泛