Live2D

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的常量池中。具体参考

https://stackoverflow.com/questions/937933/where-are-generic-types-stored-in-java--class-files/937999#937999

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等方法,说明这是一个只读集合,所以这样保护了泛型的安全。

posted @ 2022-08-25 17:33  没有梦想的java菜鸟  阅读(145)  评论(0编辑  收藏  举报