java真的有泛型?别被骗了!让我带你揭秘java泛型骗局
我们在使用java集合时候经常会用到泛型,但是你真的了解java泛型吗?接下来我将带领你揭秘java泛型骗局!
1.java可以声明泛型数组吗?
我们都知道在java中声明一个普通数组,但是你知道如何声明一个泛型数组吗?
先来看一个简单的例子,Animals是 Cat的父类,思考下Animals[]和Cat[] ,以及 List<Animals>和List<Cat>是什么关系?
Cat[] cat = new Cat[10];
// 编译通过
Animals[] animals = cat;
// 编译通过 运行报错
animals[0]=new Dog();
List<Cat> catList = new ArrayList();
// 编译报错
List<Animals>=catList;
这里我们会发现一个奇怪的问题 ,Cat[] 类型的值可以赋值给 Animals[]类型,而 List<Cat>类型赋值给 List<Animals>在编译阶段就报错了,这是其中有什么不同吗?在解释为什么java无法声明泛型数组前,我们先看一下下面的例子
Cat[] cat = new Cat[10];
List<Cat> catList = new ArrayList();
System.out.println(cat.getClass());
System.out.println(catList.getClass());
运行结果
class [LCat;
class java.util.ArrayList
我们可以发现,数组在运行时可以获取自身类型,而List<Cat>在运行时只能知道自己是一个List,而无法获取参数类型。这说明java的泛型其实是泛型擦除,也就是伪泛型。
这里就涉及到一个关键点,java的数组是协变的,而List是不变的。协变的意思就是 任意类A和B,若A是B的父类,则A[]也是B[]的父类。如果给数组加上泛型,就会无法满足协变原则,因为无法在运行时知道数组的类型。所以java是无法声明泛型数组的。
2.java的泛型为什么要通过泛型擦除来实现?
java有一大特性,向后兼容,就是老版本的java文件可以运行在新版本的jvm上。java一开始并没有泛型的,在jdk1.5以前,程序中会出现大量以下的代码:
List list = new ArrayList();
一般在没有泛型的语言上支持泛型,有两种方式,以集合为例:
- 全新设计一个新的集合框架,优点就是不需要考虑兼容老代码,缺点是需要适应新的语法,更严重可能无法改造老的业务代码
- 在老的集合框架上进行改造,添加一些特性,兼容老代码的前提下,支持泛型
java选择了第二种,主要由以下两种原因:
- 在jdk1.5以前已经有大量非泛型代码存在了,如果不兼容它们,会让使用者抗拒升级。
- jdk.1.1 升级到 jdk1.2中 Vector 到 ArrayList ,HashTable 到 HashMap ,引起大量使用者不满
所以java为了填补自己以前的坑,选择了一种比较别扭的方式实现泛型,就是类型擦除。
那么,为什么使用类型擦除可以实现上面所说的新老代码兼容问题呢?
我们看下面两行代码
List list = new ArrayList();
List<String> list2 = new ArrayList();
编译后的字节码内容如下:
L0
LINENUMBER 13 L0
NEW java/util/ArrayList
DUP
INVOKESPECIAL java/util/ArrayList.<init> ()V
ASTORE 1
L1
LINENUMBER 14 L1
NEW java/util/ArrayList
DUP
INVOKESPECIAL java/util/ArrayList.<init> ()V
ASTORE 2
我们发现两种创建集合的方式编译后的字节码是完全一样的,这也就说明低版本编译的class文件在高版本上运行不会出现问题,那么既然是类型擦除,java是怎么保证泛型的特性呢,比如类型检查、类型自动转换?
我们来看看java是怎么做的
List<String> list = new ArrayList();
list.get(0);
点进get方法,我们发现java是通过类型强制转换来保证相关泛型的特性
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
public E get(int index) {
Objects.checkIndex(index, size);
return elementData(index);
}
3.类型擦除的矛盾
在通常情况下我们并不在意它的类型是否被擦除,但是在有些场景,我们需要知道运行时泛型参数类型,比如序列化/反序列化,我们应该怎么办?
我们可以使用匿名内部类的方式来获取泛型类型的信息
ArrayList<String> list = new ArrayList<String>();
ArrayList<String> list2 = new ArrayList<String>(){};
System.out.println(list.getClass().getGenericSuperclass());
System.out.println(list2.getClass().getGenericSuperclass());
运行结果
java.util.AbstractList<E>
java.util.ArrayList<java.lang.String>
发现使用匿名内部类可以得到泛型的信息,泛型不是被擦除了吗?为什么使用匿名内部类就可以获取到?
其实泛型类型擦除并不是将全部的类型信息都擦除,还是会将类型信息放在对应的class的常量池中。具体参考
4.为什么在java中List<String>不能赋值给List<Object>
我们利用反证法来说明为什么List<String>不能赋值给List<Object>
List<String> strList = new ArrayList<String>();
// 假设可以这么赋值
List<Object> objList = strList;
objList.add(111);
// 这里将会报错
String s = strList.get(0);
如果允许List<String>不能赋值给List<Object>,那么泛型将不再保证类型安全,而设计泛型就是为了保证 类型安全,所以不支持这种行为。
但是我们发现在kotlin中,这么赋值是可以的。
val strList = ArrayList<String>()
// 编译通过
val objList: List<Any> = strList
这又是为什么呢?kotlin为什么支持这种写法?这样泛型不就不安全了吗?我们可以跟踪一下源代码
public interface List<out E> : Collection<E> {
// Query Operations
override val size: Int
override fun isEmpty(): Boolean
override fun contains(element: @UnsafeVariance E): Boolean
override fun iterator(): Iterator<E>
// Bulk Operations
override fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean
// Positional Access Operations
/**
* Returns the element at the specified index in the list.
*/
public operator fun get(index: Int): E
// Search Operations
/**
* Returns the index of the first occurrence of the specified element in the list, or -1 if the specified
* element is not contained in the list.
*/
public fun indexOf(element: @UnsafeVariance E): Int
/**
* Returns the index of the last occurrence of the specified element in the list, or -1 if the specified
* element is not contained in the list.
*/
public fun lastIndexOf(element: @UnsafeVariance E): Int
// List Iterators
/**
* Returns a list iterator over the elements in this list (in proper sequence).
*/
public fun listIterator(): ListIterator<E>
/**
* Returns a list iterator over the elements in this list (in proper sequence), starting at the specified [index].
*/
public fun listIterator(index: Int): ListIterator<E>
// View
/**
* Returns a view of the portion of this list between the specified [fromIndex] (inclusive) and [toIndex] (exclusive).
* The returned list is backed by this list, so non-structural changes in the returned list are reflected in this list, and vice-versa.
*
* Structural changes in the base list make the behavior of the view undefined.
*/
public fun subList(fromIndex: Int, toIndex: Int): List<E>
}
/**
* A generic ordered collection of elements that supports adding and removing elements.
* @param E the type of elements contained in the list. The mutable list is invariant in its element type.
*/
public interface MutableList<E> : List<E>, MutableCollection<E> {
// Modification Operations
/**
* Adds the specified element to the end of this list.
*
* @return `true` because the list is always modified as the result of this operation.
*/
override fun add(element: E): Boolean
override fun remove(element: E): Boolean
// Bulk Modification Operations
/**
* Adds all of the elements of the specified collection to the end of this list.
*
* The elements are appended in the order they appear in the [elements] collection.
*
* @return `true` if the list was changed as the result of the operation.
*/
override fun addAll(elements: Collection<E>): Boolean
/**
* Inserts all of the elements of the specified collection [elements] into this list at the specified [index].
*
* @return `true` if the list was changed as the result of the operation.
*/
public fun addAll(index: Int, elements: Collection<E>): Boolean
override fun removeAll(elements: Collection<E>): Boolean
override fun retainAll(elements: Collection<E>): Boolean
override fun clear(): Unit
// Positional Access Operations
/**
* Replaces the element at the specified position in this list with the specified element.
*
* @return the element previously at the specified position.
*/
public operator fun set(index: Int, element: E): E
/**
* Inserts an element into the list at the specified [index].
*/
public fun add(index: Int, element: E): Unit
/**
* Removes an element at the specified [index] from the list.
*
* @return the element that has been removed.
*/
public fun removeAt(index: Int): E
// List Iterators
override fun listIterator(): MutableListIterator<E>
override fun listIterator(index: Int): MutableListIterator<E>
// View
override fun subList(fromIndex: Int, toIndex: Int): MutableList<E>
}
可以发现out关键字,这个关键字可以理解为这个集合是一个只读集合,我们发现List 里并没有定义 add、remove、replace等方法,说明这是一个只读集合,所以这样保护了泛型的安全。