ArrayList<Integer> 采用 String

public class Main {
    public static void main(String[] args) {

        ArrayList<Integer> ar = new ArrayList<Integer>();
        List l = new ArrayList();
        l.add("a");
        l.add("b");
        ar.addAll(l);
        System.out.println(ar);
    }
}

输出:[a,b]

您无法直接添加到 ,但可以使用它。StringArrayList<Integer> araddAll()

我们如何将类型已指定为 ?任何人都可以强调明确的实现细节及其背后的原因吗?StringArrayListInteger


答案 1

但是,我们如何将字符串添加到类型已指定为整数的数组列表中呢?

因为Java泛型是为向后兼容而设计的,基本上是类型擦除和原始类型。

在执行时,没有这样的东西 - 只有.您使用的是 raw 类型 ,因此编译器不会在编译时或添加执行时强制转换时执行任何常规检查。ArrayList<Integer>ArrayListList

编译器确实警告你,你正在做不安全的事情:

Note: Main.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

...当您使用相关标志重新编译时,它将警告所有内容,包括可能最令人惊讶的行:

ar.addAll(l);

编译方面,这让我有些惊讶 - 我相信它有效地相信它是一个真正的,当我们知道它不是。ListCollection<? extends Integer>

如果你避免使用原始类型,这种混乱就会消失。


答案 2

这更多的是关于Java类型系统中原始类型和泛型类型的混合,而不是类型擦除。让我从问题中补充代码片段:

    ArrayList<Integer> ar = new ArrayList<Integer>();
    List l = new ArrayList();      // (1)
    l.add("a");
    l.add("b");
    ar.addAll(l);                  // (2)
    System.out.println(ar);
    Integer i = ar.get(0);         // (3)

使用今天擦除的泛型,行(3)抛出。如果重新定义了 Java 的泛型,则很容易假设运行时类型检查会导致在第 (2) 行引发异常。这将是一种可能的仿制药设计,但其他设计可能不会进行这种检查。为什么不呢?主要出于同样的原因,我们今天删除了泛型:迁移兼容性。ClassCastException

Neal Gafter在他的文章Reified Generics for Java中观察到,泛型有很多不安全的使用,转换不正确,等等。今天,即使在泛型被引入十多年后,我仍然看到大量原始类型的使用。(不幸的是,包括Stack Overflow。无条件地执行重新定义的泛型类型检查会破坏大量代码,这当然会对兼容性造成很大的打击。

任何现实的通用再化提案都必须在选择加入的基础上提供再化,例如通过子类型(如Gafter的提案)或通过注释(Gerakios,Biboudis,Smaragdakis)。使用 Java 注释的已初始化类型参数。[PDF]GPSE 2013),它必须决定如何处理原始类型。完全禁止原始类型似乎是完全不切实际的。反过来,有效地允许原始类型意味着有一种方法可以规避泛型类型系统。

(这种决定不是轻率作出的。我目睹了类型理论家之间的激烈匹配,其中一位抱怨Java的类型系统不健全。对于类型理论家来说,这是最严重的侮辱。

从本质上讲,这就是此代码的作用:它通过使用原始类型绕过泛型类型检查好坏处。即使重新定义了Java的泛型,检查也可能不在第(2)行完成。在一些已初始化的泛型设计下,代码的行为可能与今天完全相同:在第 (3) 行引发异常。

Jon Skeet的回答中,他承认有点惊讶,在第(2)行,编译器信任该列表包含正确类型的元素。这实际上与信任无关 - 毕竟,编译器确实在这里发出警告。更多的是编译器说,“好吧,你使用的是原始类型,你自己。如果你晚点,那不是我的错。不过,这也是为了兼容目的而允许原始类型,而不是擦除。lClassCastException