V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
king1101
V2EX  ›  Java

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

  •  
  •   king1101 · 2019-04-10 15:29:02 +08:00 · 3050 次点击
    这是一个创建于 2031 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

    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,这样就可以编译通过了,这其中是什么原因的?

    13 条回复    2019-04-11 12:13:18 +08:00
    dreamerfable
        1
    dreamerfable  
       2019-04-10 15:51:23 +08:00   ❤️ 1
    运行时再编译期之后。运行时擦除,编译期还没有擦除,所以编译检查时就报错了。
    peyppicp
        2
    peyppicp  
       2019-04-10 16:01:12 +08:00   ❤️ 1
    1 都说得对
    exonuclease
        3
    exonuclease  
       2019-04-10 16:40:30 +08:00
    编译的时候只需要静态分析的时候就能看到这个错误 运行时是没有这个类型信息的
    letianqiu
        4
    letianqiu  
       2019-04-10 18:43:08 +08:00   ❤️ 1
    ArrayList<Object>和 ArrayList<String>是没有关系的,所以会报类型不匹配的错误。ArrayList 可以理解为 ArrayList<?>。
    wsxyeah
        5
    wsxyeah  
       2019-04-10 20:30:19 +08:00 via iPhone
    ArrayList<Object> 是允许 add 一个 Object 进去的,ArrayList<String> 不行
    nicreve
        6
    nicreve  
       2019-04-10 20:31:00 +08:00   ❤️ 1
    这个其实是 Java 里协变的概念。
    List 不是协变的,即 A 是 B 的子类不等于 A 的 List 是 B 的 List 的“子类” List。
    而数组是协变的,所以把例子里的 List 改成数组就不会报错。
    geelaw
        7
    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
        8
    shalk  
       2019-04-10 20:53:55 +08:00   ❤️ 1
    这个报错,因为编译不通过。
    为什么 java 认为他们不是一个类型,因为 @nicreve #6 解释。

    至于类型擦除,还没发生,类型擦除也是发生在编译期间,
    运行的时候,发现元素的类型信息没有了。
    dadadajiba
        9
    dadadajiba  
       2019-04-10 21:00:47 +08:00
    https://www.v2ex.com/t/553854#reply1 被骗钱了,大家帮忙看看想想办法
    staticer
        10
    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
        11
    Leammin  
       2019-04-11 01:02:17 +08:00 via Android   ❤️ 1
    其实泛型要结合历史原因才比较好理解:以前是没泛型的,以前的 ArrayList 就是现在不带尖括号(泛型)的 ArrayList,存取对象直接就是 Object,但是每次取出对象都要进行强转类型,又因为一个 list 装的总是同一个类型的元素,所以有大量重复代码。因此后来增加了泛型,由*编译器*来帮我们做限制存取类型和强转类型的工作,如果类型不匹配,是由编译器给我们报错。
    可以这么理解:泛型仅仅是为了在编译时帮我们做限制类型和强制转换类型,而不会存储泛型类型(尖括号里边的 T )的任何信息(包括是否父子类等信息)。所以说 ArrayList、ArrayList<Object>、ArrayList<String>是同一个类型,但是因为编译器帮我们做了类型限制,所以 ArrayList<Object>和 ArrayList<String>之间是不能互相转换的;而 ArrayList 不带任何泛型表示不接受类型限制,所以另外两个可以直接转换为它,但它不能直接转换为那两个;这也是为了兼容以前没有泛型时的写法,现在不推荐这种写法,如果不能确定类型,那么可以使用 ArrayList<?>。
    king1101
        12
    king1101  
    OP
       2019-04-11 09:12:53 +08:00
    懂了,谢谢大家
    Saltyx
        13
    Saltyx  
       2019-04-11 12:13:18 +08:00 via Android
    泛型是不可变的
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2807 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 00:30 · PVG 08:30 · LAX 17:30 · JFK 20:30
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.