ArrayList<Integer> 接受字符串
Posted
技术标签:
【中文标题】ArrayList<Integer> 接受字符串【英文标题】:ArrayList<Integer> takes String 【发布时间】:2015-05-27 19:10:55 【问题描述】: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]
你不能直接将String
添加到ArrayList<Integer> ar
,但是使用addAll()
是可以的。
我们如何将String
添加到类型已经指定为Integer
的ArrayList
?任何人都可以突出明确的实施细节及其背后的原因吗?
【问题讨论】:
必须说 JAVA 和 C# 之间在这方面有很大的不同... 简短的总结请参考这个链接:***.com/questions/355060/c-sharp-vs-java-generics What is the difference between ArrayList<?>, ArrayList, ArrayList<Object>?的可能重复 What is a raw type and why shouldn't we use it?的可能重复 【参考方案1】:但是我们如何将字符串添加到类型已经指定为整数的数组列表中呢?
因为 Java 泛型是为向后兼容而设计的,基本上是类型擦除和原始类型。
在执行时,没有ArrayList<Integer>
这样的东西——只有ArrayList
。您使用的是原始类型 List
,因此编译器不会在编译时或添加执行时强制转换时进行任何正常检查。
编译器确实警告你你正在做不安全的事情:
Note: Main.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
...当您使用相关标志重新编译时,它会警告所有内容,包括可能最令人惊讶的行:
ar.addAll(l);
那是在编译方面让我有些吃惊的地方——我相信它实际上是在相信List
确实是Collection<? extends Integer>
,但我们知道它不是。
如果你避免使用原始类型,这种混乱就会消失。
【讨论】:
补充一点,在查看java.util.ArrayList#addAll
的代码时,你可以简单地看到它只是创建一个集合的对象数组,并使用 System.arraycopy 将该数组复制到另一个数组中对象。
这不是因为类型擦除,而是因为泛型集合被设计为向后可与非泛型集合一起使用。编译器很容易在 addAll 中不接受无类型列表,但期望用户一次将所有代码转换为泛型并不是很实用。警告是和解。
此外,如果您的代码期望该数组仅包含整数,则在运行时您将收到 ClassCastException。 for (final Integer i : ar) System.out.println(i);
@Benjamin:虽然没有类型擦除,但可以在执行时检测到错误,当然……所以这并不是说类型擦除是无关紧要的,除非我错过了你的评论。跨度>
@StuartMarks:我认为可以说它编译是因为原始类型,但它执行是因为擦除。当然,它们都是非常相关的主题 - 我会相应地编辑我的答案。【参考方案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) 行抛出 ClassCastException
。如果 Java 的泛型被具体化,很容易假设运行时类型检查会导致在第 (2) 行抛出异常。那将是具体化泛型的一种可能设计,但其他设计可能不会进行这种检查。为什么不?主要出于同样的原因,我们今天已经删除了泛型:迁移兼容性。
Neal Gafter 在他的文章Reified Generics for Java 中观察到,泛型有很多不安全的使用、不正确的强制转换等等。今天,即使在泛型引入十多年后,我仍然看到很多原始类型的使用。 (不幸的是,包括在 Stack Overflow 上。)无条件地执行具体的泛型类型检查会破坏 大量 代码,这当然会对兼容性造成很大打击。
任何现实的通用具体化提案都必须在选择加入的基础上提供具体化,例如通过子类型(如在 Gafter 的提案中)或通过注释(Gerakios、Biboudis、Smaragdakis。Reified Type Parameters Using Java Annotations. [PDF] GPSE 2013。 ),它必须决定如何处理原始类型。完全禁止原始类型似乎完全不切实际。反过来,有效地允许原始类型意味着有一种方法可以绕过泛型类型系统。
(这种决定不是轻易做出的。我目睹了类型理论家之间的争吵,其中一个抱怨 Java 的类型系统不健全。对于类型理论家,这是最严重的侮辱。)
本质上,这就是这段代码的作用:它通过使用原始类型绕过泛型类型检查。即使 Java 的泛型被具体化,也可能不会在第 (2) 行进行检查。在某些具体化的泛型设计下,代码的行为可能与现在完全相同:在第 (3) 行抛出异常。
在Jon Skeet's answer 中,他承认在第(2) 行编译器相信列表l
包含正确类型的元素感到有些惊讶。这与信任无关——毕竟,编译器确实会在此处发出警告。更多的是编译器说,“好吧,你使用的是原始类型,你自己。如果你以后得到ClassCastException
,那不是我的错。”不过,这也是为了兼容性目的而允许使用原始类型,而不是擦除。
【讨论】:
【参考方案3】:您使用的是原始类型。如果您使用List<String> l = new ArrayList<>()
,您会发现您的代码将不再编译。原始类型的存在只是为了向后兼容,不应在新代码中使用。
【讨论】:
【参考方案4】:Java 诞生的时候还没有泛型(即由另一个类参数化的类)。添加泛型时,为了保持兼容性,决定不更改 Java 字节码和类文件格式。因此,编译器将泛型类转换为非泛型类。这意味着 ArrayList 实际上存储了 Object 类的实例,因此它也可以接受 String 的实例(即 Object 的子类)。编译器不能总是检测到误用。
【讨论】:
【参考方案5】:您遗漏了第二个列表的类型。省略第一个列表的类型,您也可以这样做:
ArrayList ar = new ArrayList();
ar.add(Integer.valueOf(42));
ar.add("Hello");
该类型仅在编译时考虑。这就是为什么您可能会在 eclipse 中收到警告。在字节码中不考虑类型,您的应用程序运行时不会出现异常。
【讨论】:
【参考方案6】:我们只能在编译期间实现类型安全。在运行时类型橡皮擦将擦除所有这些东西,它将是一个普通的字节码,即它就像我们没有泛型一样。
【讨论】:
以上是关于ArrayList<Integer> 接受字符串的主要内容,如果未能解决你的问题,请参考以下文章
ArrayList <Integer> 与 get/remove 方法