为什么此方法允许将double存储在Integer类型的ArrayList中?

时间:2019-05-11 03:27:08

标签: java generics reflection

我有一个方法应该返回具有给定条目的给定集合类的实例。实现如下所示。

public static <E, C extends Collection<E>> C
         initalizeAndAddToCollection(Class<C> clazz, E... entries) {
    //Collection object
    Collection<E> collection;
    //try to invoke default constructor of C
    try {
        collection = clazz.getDeclaredConstructor().newInstance();
    } catch (InstantiationException | IllegalAccessException |
             InvocationTargetException | NoSuchMethodException e) {
        throw new RuntimeException();
    }
    //Add elements to collection
    for (E entry: entries)
        collection.add(entry);
    return (C) collection;
}

问题是即使double不能存储在类型Integer的列表中,以下代码也会运行

 //Create and instantiate an ArrayList with element 1.0
 ArrayList<Integer> list = initalizeAndAddToCollection(ArrayList.class, 1.0);
 System.out.print(list.get(0));

为什么该代码运行,以及如何使之运行,从而导致编译或运行时错误?

编辑:我注意到list.get(0).getClass()确实会产生异常,但是我不确定为什么会这样(或者为什么以前的代码不会)。

2 个答案:

答案 0 :(得分:2)

在Java中,泛型是仅用于编译时的功能,因为它们是通过type erasure实现的。因此,尽管您可能认为自己是通过ArrayList<Integer>调用在运行时创建newInstance的,但实际上您实际上只是在创建ArrayList

简而言之,反射破坏了Java中泛型的类型安全性。

答案 1 :(得分:1)

此代码在编译时会发出警告。警告说:

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

实际上,当您执行此操作时,它会产生4条警告消息:

MyTest.java:5: warning: [unchecked] Possible heap pollution from parameterized vararg type E
        initalizeAndAddToCollection(Class<C> clazz, E... entries) {
                                             ^
  where E,C are type-variables:
    E extends Object declared in method <E,C>initalizeAndAddToCollection(Class<C>,E...)
    C extends Collection<E> declared in method <E,C>initalizeAndAddToCollection(Class<C>,E...)


MyTest.java:18: warning: [unchecked] unchecked cast
        return (C) collection;
                   ^
  required: C
  found:    Collection<E>
  where E,C are type-variables:
    E extends Object declared in method <E,C>initalizeAndAddToCollection(Class<C>,E...)
    C extends Collection<E> declared in method <E,C>initalizeAndAddToCollection(Class<C>,E...)
MyTest.java:23: warning: [unchecked] unchecked method invocation: method initalizeAndAddToCollection in class MyTest is applied to given types
             initalizeAndAddToCollection(ArrayList.class, 1.0);
                                        ^
  required: Class<C>,E[]
  found: Class<ArrayList>,double
  where C,E are type-variables:
    C extends Collection<E> declared in method <E,C>initalizeAndAddToCollection(Class<C>,E...)
    E extends Object declared in method <E,C>initalizeAndAddToCollection(Class<C>,E...)
MyTest.java:23: warning: [unchecked] unchecked conversion
             initalizeAndAddToCollection(ArrayList.class, 1.0);
                                        ^
  required: ArrayList<Integer>
  found:    ArrayList
4 warnings

这些警告说明了为什么编译此看似不正确的代码。您正在执行编译器告诉您不正确的事情。


  

为什么此代码运行。

因为它实际上并没有破坏运行时类型安全。

  • 类型擦除意味着该集合实际上正在存储对Object的引用。

  • 然后执行System.out.print(list.get(0));时,print调用的参数类型为Object。这意味着不需要隐式转换为Integer

  

以及如何使它导致编译或运行时错误?

如果需要编译时错误,请告诉编译器将警告视为错误。 (或检查编译输出中的警告。)

如果需要运行时错误,则可能需要在initalizeAndAddToCollection方法中添加一些显式的运行时类型检查。