10

我在这个时期正在研究仿制药,今天我发现了这个谜。

让我们考虑以下虚拟类:

    public class Main{

    public static void main(String[] args) { 
        Container<Integer> c = new Container<Integer>(); 

        c.getArray();                      //No Exception
        //c.getArray().getClass();         //Exception
        //int a = c.getArray().length;     //Exception

    } 

}


class Container<T> { 

    T[] array; 

    @SuppressWarnings("unchecked") 
    Container() { 
        array = (T[])new Object[1]; 
    } 

    void put(T item) { 
        array[0] = item; 
    } 

    T get() { return array[0]; } 

    T[] getArray() { return array; }
}  

因为擦除,在运行时,getArray() 方法的 T[] 返回类型变成了 Object[],这对我来说是完全合理的。

如果我们按原样访问该方法 (c.getArray()),则不会引发异常,但是如果我们尝试在返回的数组上调用某些方法,例如 c.Array().getClass(),或者如果我们尝试访问一个字段,例如 c.getArray().length,则抛出以下异常:

线程“main”中的异常 java.lang.ClassCastException: [Ljava.lang.Object; 不能转换为 [Ljava.lang.Integer;

为什么会抛出这个异常?为什么简单的 c.getArray() 调用也不抛出它?如果我们只是调用 getClass() 或访问长度,为什么它会尝试转换为 Integer[]?getClass() 和长度是否也不适用于 Object[]?

提前感谢您的许多(我希望)和解释性(我也希望如此)的答案。

4

3 回答 3

2

异常的原因是编译器期望 aInteger[]但接收到Object[]. 它在getArray. 这些演员在你的构造函数中发现了撒谎的、虚拟的、无效果的演员。

为了使其正确,需要实际的 T 类来创建实例。

@SuppressWarnings("unchecked")
Container(Class<T> type) {
    array = (T[]) Array.newInstance(type, 10);
}


    Container<Integer> c = new Container<Integer>(Integer.class); 

    c.getArray();
    Class<?> t = c.getArray().getClass();
    System.out.println(t.getName());
    int a = c.getArray().length;

同样,这里仍然是一个“不安全”的强制转换,T[]但这是不可避免的,因为这是Array.newInstancen 维数组的低级方法,例如:

(double[][][][][][]) Array.newInstance(double.class, 3, 3, 3, 3, 3, 6);
于 2016-07-08T13:25:47.397 回答
1

我无法在 JLS 中找到确切的位置,说明这是这种行为,但我认为原因是这样的:

表达方式:

c.getArray().getClass();

大致相当于:

Integer[] arr = (Integer[]) c.getArray();
arr.getClass();

由于类型擦除,必须添加演员表。此隐式强制转换checkcast在字节码中添加了一条指令,该指令以 a 失败ClassCastException,因为c.getArray()它的类型为Object[]

查看字节码:

static void implicit() {
  Container<Integer> c = new Container<Integer>();
  c.getArray().getClass(); //Exception
}

static void explicit() {
  Container<Integer> c = new Container<Integer>();
  Integer[] arr = (Integer[]) c.getArray();
  arr.getClass(); //Exception
}

我们得到:

  static void implicit();
    Code:
       0: new           #2                  // class Container
       3: dup
       4: invokespecial #3                  // Method Container."<init>":()V
       7: astore_0
       8: aload_0
       9: invokevirtual #4                  // Method Container.getArray:()[Ljava/lang/Object;
      12: checkcast     #5                  // class "[Ljava/lang/Integer;"
      15: invokevirtual #6                  // Method java/lang/Object.getClass:()Ljava/lang/Class;
      18: pop
      19: return

  static void explicit();
    Code:
       0: new           #2                  // class Container
       3: dup
       4: invokespecial #3                  // Method Container."<init>":()V
       7: astore_0
       8: aload_0
       9: invokevirtual #4                  // Method Container.getArray:()[Ljava/lang/Object;
      12: checkcast     #5                  // class "[Ljava/lang/Integer;"
      15: checkcast     #5                  // class "[Ljava/lang/Integer;"
      18: astore_1
      19: aload_1
      20: invokevirtual #6                  // Method java/lang/Object.getClass:()Ljava/lang/Class;
      23: pop
      24: return

所以explicit版本的唯一区别是三个指令:

      15: checkcast     #5                  // class "[Ljava/lang/Integer;"
      18: astore_1
      19: aload_1

据我了解,这仅仅是因为将其显式存储在变量中。

于 2016-07-08T13:43:40.350 回答
1

当您执行不安全的未经检查的强制转换时,它可能会或可能不会在某处导致异常。您不能保证在某处获得异常。

在这种情况下,您是否得到异常取决于编译器是否在擦除的代码中插入了强制转换以将调用结果强制转换为Integer[]. 在这种情况下,似乎在第二种和第三种情况下插入了演员表,而不是第一种情况。

在这三种情况中的每一种情况下,编译器都可以插入强制转换(因为允许假设结果是Integer[]或不插入强制转换(因为表达式的使用方式只需要Object[]在所有三种情况下)。是否插入强制转换取决于特定的编译器实现来决定。

为什么这个编译器不会在第一种情况下插入强制转换,而在第二种和第三种情况下插入强制转换?一个明显的解释是,在第一种情况下,结果显然是未使用的,因此很容易确定强制转换是不必要的。在第二种和第三种情况下,要确定强制转换是不必要的,需要查看表达式是如何使用的,以了解它是否也适用于Object[]; 这是一个相当复杂的分析。编译器作者可能选择了一种简单的方法,即仅在未使用结果时才跳过强制转换。

另一个编译器可能会在所有三种情况下都插入强制转换。另一个编译器可能在所有三种情况下都没有强制转换。你不能依赖它。

于 2016-07-08T18:41:11.740 回答