简体   繁体   English

Java擦除如何影响通用数组?

[英]How is the Java erasure affecting the generic arrays?

I'm studying generics in this period and today I've found this mystery for me. 我正在研究这一时期的仿制药,今天我已经为我找到了这个谜。

Let's consider the following dummy class: 让我们考虑以下虚拟类:

    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; }
}  

Because of erasure, at runtime, the T[] return type of the getArray() method is turned into a Object[], which is completely reasonable to me. 由于擦除,在运行时,getArray()方法的T []返回类型变为Object [],这对我来说是完全合理的。

If we access that method as it is (c.getArray()) no Exceptions are thrown, but if we try to call some methods on the returned array, for example c.Array().getClass(), or if we try to access to a field, for example c.getArray().length, then the following exception is thrown: 如果我们按原样访问该方法(c.getArray())没有抛出异常,但是如果我们尝试在返回的数组上调用某些方法,例如c.Array()。getClass(),或者如果我们尝试访问一个字段,例如c.getArray()。length,然后抛出以下异常:

Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; 线程“main”中的异常java.lang.ClassCastException:[Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer; 无法转换为[Ljava.lang.Integer;

Why is this Exception thrown? 为什么抛出此异常? Why is it not thrown also for the simple c.getArray() call? 为什么不对简单的c.getArray()调用抛出它? Why is it trying to cast to Integer[] if we are simply calling getClass() or accessing length? 如果我们只是调用getClass()或访问长度,为什么要尝试强制转换为Integer []? Are getClass() and length not available also for Object[]? 对于[],getClass()和长度是否也不可用?

Thanks in advance for your many (I hope) and explanatory (I hope this too) answers. 在此先感谢您的许多(我希望)和解释(我也希望这样)答案。

The reason for the exception is that the compiler expects a Integer[] but receives an Object[] . 异常的原因是编译器需要Integer[]但接收Object[] It added a run-time cast - at the call sites of getArray . 它在getArray的调用站点添加了一个运行时getArray Those casts discovered the lying, dummy, no-effect cast in your constructor. 那些演员在你的构造函数中发现了谎言,虚拟,无效的演员。

For it to be correct, one needs the actual class of T, in order to create instances. 为了使其正确,需要实际的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;

Also here remains an "unsafe" cast to T[] but this is unavoidable as Array.newInstance is a low-level method for n-dimensional arrays like in: 此处仍然是对T[]的“不安全” Array.newInstance但这是不可避免的,因为Array.newInstance是n维数组的低级方法,如:

(double[][][][][][]) Array.newInstance(double.class, 3, 3, 3, 3, 3, 6);

I have not been able to find the exact place in the JLS which says this is the behaviour, but I think the reason is something like this: 我无法在JLS中找到确切的位置,说这是行为,但我认为原因是这样的:

The expression: 表达方式:

c.getArray().getClass();

is roughly equivalent to: 大致相当于:

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

where the cast has to be added because of type erasure. 由于类型擦除,必须添加演员表。 This implicit cast adds a checkcast instruction in the bytecode, which fails with a ClassCastException , since c.getArray() is of type Object[] . 此隐式checkcast在字节码中添加了checkcast指令,该指令因ClassCastException而失败,因为c.getArray()的类型为Object[]

Looking at the bytecode for: 查看字节码:

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
}

we get: 我们得到:

  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

So the only difference in the explicit version are the three instructions: 因此, explicit版本的唯一区别是三条指令:

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

Which are there only because of explicitly storing this in a variable, as far as I understand it. 根据我的理解,这只是因为明确地将它存储在变量中。

When you do an unsafe unchecked cast, it may or may not cause an exception somewhere. 当您执行不安全的未经检查的强制转换时,它可能会或可能不会在某处导致异常。 You are not guaranteed to get an exception somewhere. 你不能保证在某个地方得到例外。

In this case, whether you get an exception depends on whether the compiler inserted a cast in the erased code to cast the result of the call to Integer[] . 在这种情况下,是否获得异常取决于编译器是否在已擦除代码中插入强制转换以将调用结果强制转换为Integer[] In this case, it seems a cast was inserted in the second and third case but not the first case. 在这种情况下,似乎在第二和第三种情况下插入了一个演员而不是第一种情况。

In each of the three cases, the compiler is allowed to insert a cast (because it is allowed to assume that the result is Integer[] or not insert a cast (because the expression is used in such a way that only requires Object[] in all three). Whether to insert a cast or not is up to the particular compiler implementation to decide. 在这三种情况中的每一种情况下,允许编译器插入一个强制转换(因为它允许假设结果是Integer[]或者不插入强制转换(因为表达式的使用方式只需要Object[]在所有三个中。是否插入一个强制转换取决于特定的编译器实现来决定。

Why would this compiler not insert a cast in the first case and insert a cast in the second and third cases? 为什么这个编译器不会在第一种情况下插入强制转换并在第二种和第三种情况下插入强制转换? One obvious explanation would be that in the first case, the result is obviously unused, so it is very simple to determine that a cast is unnecessary. 一个明显的解释是,在第一种情况下,结果显然是未使用的,因此确定转换是不必要的非常简单。 In the second and third cases, to determine that a cast is unnecessary would require looking at how the expression is used to see that the it will also work with Object[] ; 在第二种和第三种情况下,要确定不需要强制转换,需要查看表达式如何用于查看它是否也适用于Object[] ; and this is a rather complicated analysis. 这是一个相当复杂的分析。 The compiler authors probably opted for a simple approach where they skip the cast only when the result is unused. 编译器作者可能选择了一种简单的方法,只有在结果未使用时才会跳过强制转换。

Another compiler might insert casts in all three cases. 另外一个编译器可能会在所有三种情 And another compiler might have no casts in all three cases. 并且在所有三种情况下,另一个编译器可能没有强制转换。 You cannot rely on it. 你不能依赖它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM