Java泛型 - 可以混合使用类型吗?

3
我之前进行了一些测试,但无法找到为什么这段代码会执行以下操作的解释:
public class Test {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList(Arrays.asList(Double.valueOf(0.1234)));
        System.out.println(list.get(0)); //prints 0.1234
        Object d = list.get(0);
        System.out.println(d.getClass()); // prints class java.lang.Double
        System.out.println(list.get(0).getClass()); // ClassCastException
    }
}

这引出了几个问题:

  • 为什么List<Integer>首先接受Double(它是否应该编译)?
  • 为什么第二个打印可以工作而第三个不行,尽管它们看起来在做同样的事情?

编辑
我理解以下两个语句:

List aList = new ArrayList(); //I can add any objects in there
List<Integer> aList = new ArrayList<Integer>(); //I can only add something that extends Integer in there

但是我不明白为什么这个程序被授权,并且在运行时在某种程度上确实起作用,尽管一些操作会产生ClassCastException - 我本来期望在上面发布的代码的第一行就会产生ClassCastException:

List<Integer> aList = new ArrayList(); //I can any objects in there

在您的编辑中:这样做可以支持早期泛型之前的Java版本。(Java非常注重向后兼容性。) - Angelo Fuchs
经过一些思考,我修改了下面的答案。我认为现在更精确地回答了为什么它能够按照这种方式工作的问题。 - Angelo Fuchs
3个回答

6
这是:
new ArrayList(Arrays.asList(Double.valueOf(0.1234)))

创建一个原始(未定义类型)的 ArrayList,您可以将任何内容放入其中。以下是正确的方法:

new ArrayList<Integer>(Arrays.asList(Double.valueOf(0.1234)))

现在不应该编译。


1
我知道正确的形式。我只是好奇为什么我发布的代码虽然声明为List <Integer>,但为什么它可以编译,并且为什么在运行时对某些操作而言会发送ClassCastException,而对其他操作则不会。 - assylias
那么这是编译器中的漏洞,允许您将ArrayList放入ArrayList<Integer>中吗?还是有原因允许这样做? - Toomai
@Toomai:这是为了支持旧代码,在泛型引入之前。请参见http://en.wikipedia.org/wiki/Generics_in_Java#Problems_with_type_erasure。 - Oliver Charlesworth
3
还应该注意的是,编译原始程序时会在分配值的list行上产生有关未经检查的调用的警告。基本上,如果您使用泛型,只有当所有内容都没有任何未经检查的转换警告时,您的代码才保证不会抛出ClassCastException - ivantod
基本上你必须记住,在Java中所有的泛型都只是X<Object>,编译器会添加适当的转换。因此,如果你创建一个ArrayList<Integer>,它仍然只是一个旧式的对象ArrayList,编译器会为你添加转换。所以你可以创建一个ArrayList<Integer>,将其转换或分配给一个ArrayList<Object>,并且可以很好地处理它。 - John B

5
如果你写代码
... new ArrayList<Integer>(...

相反,它会导致编译器异常。

关于它为什么有效:

System.out.println(list.get(0)); //prints 0.1234
Object.toString() 方法在 DoubleInteger 中是相同的(由于 System.out.println() 方法需要一个 Object 参数,因此不会将其强制转换为 Integer 类型(编译器会优化掉这个转换))。
Object d = list.get(0);
System.out.println(d.getClass()); // prints class java.lang.Double

同样的道理适用于.getClass()。在这里,优化器再次删除了转换。
System.out.println(list.get(0).getClass()); // ClassCastException

这实际上从列表中创建了一个整数,但失败了。它进行强制类型转换是因为优化器认为需要这样做,因为不明显它不需要。
如果您将最后一行更改为:
    System.out.println(((Object)list.get(0)).getClass());

它运行正常 :)


所以list.get(0)被转换为Object,第二个打印语句可以工作,但是list.get(0)在list.get(0).getClass()中被转换为Integer,这在第三个打印语句中失败了。 - assylias
@assylias 基本上是这样,但你必须明白工作的那些部分之所以工作,仅仅是因为编译器的优化。进行类的转换是有成本的,当编译器看到它不必要时(因为明确请求了一个Object),它就会将其删除。 - Angelo Fuchs
@assylias 如果你在最后一行加上(Object),它也可以工作(请参见我再次编辑的答案。这里编译器也会去掉强制转换,因此不会抛出ClassCastException异常。 - Angelo Fuchs

0
第二个不起作用是因为当您使用泛型时,编译器会为您插入强制转换(您不必这样做)。编译器尝试将元素转换为Integer,因为那是列表的泛型类型。
由于您通过未经检查的添加向列表中添加了内容,因此现在列表中有一些内容在进入时没有进行检查,因此在出现问题时失败。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接