请考虑下面的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);
}
}
它编译正常但在执行时抛出以下异常:
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
at CastIssue.main(CastIssue.java:7)
另一方面,如果我向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);
}
}
为什么在第一种情况下抛出异常?
我使用的是JDK 1.8.0_40
编辑:如@wero所述,编译器在第一种情况下插入一个强制转换。我想理解为什么要插入那些演员(抱歉没有在原始问题中明确说明)。任何对JLS或JVM规范的引用都会很好。答案 0 :(得分:0)
在编译的类中,由于类型擦除,字段private T[] data
的类型为Object[]
。
但是当你访问它时(在你的第一个例子中issue.data.length
),它被转换为一个Integer数组。使用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
当您将其强制转换为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
答案 1 :(得分:0)
由于类型擦除,T
实际上是Object
的非静态方法中的CastIssue
。在main
方法中,T
已知为Integer
,因此issue.data
引用会隐式转换为Integer[]
。
Integer[]
是Object[]
的子类,同样Integer
是Object
的子类。
您可以将Integer
分配给Object
类型的变量,但不能将Object
转换/分配给Integer
类型的变量。
对于数组也是如此:您可以将Integer[]
分配给Object[]
类型的变量,但不能将Object[]
转换/赋值给{{1}类型的变量}。
那么,你是怎么做的,用泛型?
为了让代码构建Integer[]
或T
的实际实例,必须给出T[]
所应的Class
的实际T
在运行时。
您可以在构造函数中传入该类,并使用反射来创建数组:
private T[] data;
@SuppressWarnings("unchecked")
public CastIssue(Class<T> clazz) {
this.data = (T[])Array.newInstance(clazz, 1);
}
您的主要方法是:
final CastIssue<Integer> issue = new CastIssue<Integer>(Integer.class);
System.out.println(issue.data.length);
在构造函数上指定Integer
两次似乎是多余的,并且确定它可以使用菱形运算符(new CastIssue<>(Integer.class)
)折叠,但仍然...
您可以使用通用静态方法让编译器从参数中推断出类型(T
):
public static <T> CastIssue<T> create(Class<T> clazz) {
return new CastIssue<T>(clazz);
}
这样主要方法就变成了:
final CastIssue<Integer> issue = create(Integer.class);
System.out.println(issue.data.length);
这对你来说是否更好决定。