关于泛型的类型擦除机制的一个疑惑?

2019-04-10 15:29:02 +08:00
 king1101

从一篇博客上看到的,但是博主只是说类型擦除导致出错。

package simplejava;

import java.util.ArrayList;

public class ErasedTest {
    public static void main(String[] args) {
        ArrayList<String> arr = new ArrayList<String>();
        arr.add("a");
        arr.add("b");
        accept(arr);
    }

    public static void accept(ArrayList<Object> al) {
        for (Object o : al)
            System.out.println(o);
    }

}

我的理解是:泛型是编译的时候不同,运行时被擦除为原生类型,也就是 main 方法中的变量 arr 的类型变成了 ArrayList,accept 方法中的参数 al 的类型也同样变成了 ArrayList,既然这样,为什么编译的时候出现了这样的错误:

error: incompatible types: ArrayList<String> cannot be converted to ArrayList<Object>

然后,如果在 accept 方法中的参数类型从ArrayList<Object> al改成ArrayList al,这样就可以编译通过了,这其中是什么原因的?

2896 次点击
所在节点    Java
13 条回复
dreamerfable
2019-04-10 15:51:23 +08:00
运行时再编译期之后。运行时擦除,编译期还没有擦除,所以编译检查时就报错了。
peyppicp
2019-04-10 16:01:12 +08:00
1 都说得对
exonuclease
2019-04-10 16:40:30 +08:00
编译的时候只需要静态分析的时候就能看到这个错误 运行时是没有这个类型信息的
letianqiu
2019-04-10 18:43:08 +08:00
ArrayList<Object>和 ArrayList<String>是没有关系的,所以会报类型不匹配的错误。ArrayList 可以理解为 ArrayList<?>。
wsxyeah
2019-04-10 20:30:19 +08:00
ArrayList<Object> 是允许 add 一个 Object 进去的,ArrayList<String> 不行
nicreve
2019-04-10 20:31:00 +08:00
这个其实是 Java 里协变的概念。
List 不是协变的,即 A 是 B 的子类不等于 A 的 List 是 B 的 List 的“子类” List。
而数组是协变的,所以把例子里的 List 改成数组就不会报错。
geelaw
2019-04-10 20:36:07 +08:00
正确写出类型不安全的代码的方式是这样的:

ArrayList<String> stringList = new ArrayList<String>();
ArrayList erasedList = (ArrayList)stringList;
erasedList.add(new Integer(0));
shalk
2019-04-10 20:53:55 +08:00
这个报错,因为编译不通过。
为什么 java 认为他们不是一个类型,因为 @nicreve #6 解释。

至于类型擦除,还没发生,类型擦除也是发生在编译期间,
运行的时候,发现元素的类型信息没有了。
dadadajiba
2019-04-10 21:00:47 +08:00
https://www.v2ex.com/t/553854#reply1 被骗钱了,大家帮忙看看想想办法
staticer
2019-04-10 21:42:23 +08:00
ArrayList<Object>和 ArrayList<String>是没关系的。
设想一下,假如 ArrayList<Object>可以引用 ArrayList<String>。

那么,假如
void accept(ArrayList<Object> al){
al.add(new Object());
}

那么 accept(一个 ArrauList<String>对象)是不合理的。
Leammin
2019-04-11 01:02:17 +08:00
其实泛型要结合历史原因才比较好理解:以前是没泛型的,以前的 ArrayList 就是现在不带尖括号(泛型)的 ArrayList,存取对象直接就是 Object,但是每次取出对象都要进行强转类型,又因为一个 list 装的总是同一个类型的元素,所以有大量重复代码。因此后来增加了泛型,由*编译器*来帮我们做限制存取类型和强转类型的工作,如果类型不匹配,是由编译器给我们报错。
可以这么理解:泛型仅仅是为了在编译时帮我们做限制类型和强制转换类型,而不会存储泛型类型(尖括号里边的 T )的任何信息(包括是否父子类等信息)。所以说 ArrayList、ArrayList<Object>、ArrayList<String>是同一个类型,但是因为编译器帮我们做了类型限制,所以 ArrayList<Object>和 ArrayList<String>之间是不能互相转换的;而 ArrayList 不带任何泛型表示不接受类型限制,所以另外两个可以直接转换为它,但它不能直接转换为那两个;这也是为了兼容以前没有泛型时的写法,现在不推荐这种写法,如果不能确定类型,那么可以使用 ArrayList<?>。
king1101
2019-04-11 09:12:53 +08:00
懂了,谢谢大家
Saltyx
2019-04-11 12:13:18 +08:00
泛型是不可变的

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/553741

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX