[英]ClassCastException when accessing array of type Object[]
Please consider the following piece of Java code (I have taken it from real world but simplified a bit and removed irrelevant details): 请考虑以下Java代码(我从真实世界中获取了该代码,但做了一些简化,并删除了无关的细节):
public class CastIssue<T> {
@SuppressWarnings("unchecked")
private T[] data = (T[]) new Object[1];
public static void main(final String[] args) {
final CastIssue<Integer> issue = new CastIssue<>();
System.out.println(issue.data.length);
}
}
It compiles fine but throws the following exception when executed: 它可以很好地编译,但是在执行时抛出以下异常:
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
at CastIssue.main(CastIssue.java:7)
On the other hand if I add explicit cast to Object[] it works without exception: 另一方面,如果我向Object []添加显式强制转换,则可以正常工作:
public class CastIssue<T> {
@SuppressWarnings("unchecked")
private T[] data = (T[]) new Object[1];
public static void main(final String[] args) {
final CastIssue<Integer> issue = new CastIssue<>();
System.out.println(((Object[])issue.data).length);
}
}
Why does exception is thrown in the first case? 为什么在第一种情况下会引发异常?
I use JDK 1.8.0_40 我使用JDK 1.8.0_40
EDIT: as noted by @wero, compiler inserts a cast in the first case. 编辑:@wero指出,编译器在第一种情况下会插入强制类型转换。 I want to understand why that cast is inserted there (sorry for not stating this explicitly in the original question).
我想了解为什么将演员表插入那里(很抱歉,在原始问题中未明确说明)。 Any references to JLS or JVM specification would be good.
任何对JLS或JVM规范的引用都可以。
In the compiled class the type of the field private T[] data
is Object[]
due to type erasure. 在已编译的类中,由于类型擦除,字段
private T[] data
为Object[]
。 But when you access it ( issue.data.length
in your first example) it is casted to a Integer array. 但是,当您访问它时(第一个示例中的
issue.data.length
),它被issue.data.length
转换为Integer数组。 This is easy to see when you decompile the class with javap
: 使用
javap
反编译类时,这很容易看到:
public static void main(java.lang.String[]);
Code:
0: new #1 // class test/CastIssue
3: dup
4: invokespecial #24 // Method "<init>":()V
7: astore_1
8: getstatic #25 // Field java/lang/System.out:Ljava/io/PrintStream;
11: aload_1
12: getfield #14 // Field data:[Ljava/lang/Object;
15: checkcast #31 // class "[Ljava/lang/Integer;"
18: arraylength
19: invokevirtual #33 // Method java/io/PrintStream.println:(I)V
22: return
When you cast it to an Object array (your second example), the checkcast
operation is dropped and the error does not occur: 当您将其
checkcast
转换为Object数组(第二个示例)时, checkcast
操作将被丢弃,并且不会发生错误:
public static void main(java.lang.String[]);
Code:
0: new #1 // class test/CastIssue
3: dup
4: invokespecial #24 // Method "<init>":()V
7: astore_1
8: getstatic #25 // Field java/lang/System.out:Ljava/io/PrintStream;
11: aload_1
12: getfield #14 // Field data:[Ljava/lang/Object;
15: arraylength
16: invokevirtual #31 // Method java/io/PrintStream.println:(I)V
19: return
Because of type erasure, T
is actually an Object
in the non-static methods of CastIssue
. 由于类型擦除,在
CastIssue
的非静态方法中T
实际上是一个Object
。 In the main
method, T
is known to be Integer
, so the issue.data
reference is implicitly cast to an Integer[]
. 在
main
方法中,已知T
为Integer
,因此issue.data
参考隐式转换为Integer[]
。
An Integer[]
is a subclass of an Object[]
, in the same way Integer
is a subclass of an Object
. Integer[]
是Object[]
的子类,就像Integer
是Object
的子类一样。
You can assign an Integer
to a variable of type Object
, but you cannot cast/assign an Object
to a variable of type Integer
. 您可以将
Integer
分配给Object
类型的变量,但是不能将Object
强制转换/分配给Integer
类型的变量。
Same goes for the arrays: You can assign an Integer[]
to a variable of type Object[]
, but you cannot cast/assign an Object[]
to a variable of type Integer[]
. 数组也是如此:您可以将
Integer[]
分配给Object[]
类型的变量,但是不能将Object[]
强制转换/分配给Integer[]
类型的变量。
So, how do you do it, with generics? 那么,如何使用泛型呢?
In order for the code to construct an actual instance of T
, or a T[]
, it must be given the actual Class
of whatever T
is supposed to be at runtime. 为了使代码构造
T
或T[]
的实际实例,必须为它在运行时赋予T
的实际Class
。
You can pass in that class on the constructor, and use reflection to create the array: 您可以在构造函数上传递该类,并使用反射来创建数组:
private T[] data;
@SuppressWarnings("unchecked")
public CastIssue(Class<T> clazz) {
this.data = (T[])Array.newInstance(clazz, 1);
}
Your main method will then be: 您的主要方法将是:
final CastIssue<Integer> issue = new CastIssue<Integer>(Integer.class);
System.out.println(issue.data.length);
Specifying Integer
twice on the constructor seems redundant, and sure it can be collapsed with the diamond operator ( new CastIssue<>(Integer.class)
), but still... 在构造函数上两次指定
Integer
似乎是多余的,并且确保可以使用菱形运算符( new CastIssue<>(Integer.class)
)将其折叠,但是仍然...
You can use a generic static method to let the compiler infer the type ( T
) from the parameter: 您可以使用通用静态方法让编译器从参数推断类型(
T
):
public static <T> CastIssue<T> create(Class<T> clazz) {
return new CastIssue<T>(clazz);
}
This way the main method becomes: 这样,主要方法变为:
final CastIssue<Integer> issue = create(Integer.class);
System.out.println(issue.data.length);
Whether that's better is for you to decide. 是否更好取决于您的决定。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.