![从字节码看java类型转换【 深入理解 (T[]) new Object[size] 】 从字节码看java类型转换【 深入理解 (T[]) new Object[size] 】](https://image.shishitao.com:8440/aHR0cHM6Ly9ia3FzaW1nLmlrYWZhbi5jb20vdXBsb2FkL2NoYXRncHQtcy5wbmc%2FIQ%3D%3D.png?!?w=700&webp=1)
我们都知道,java中对类型的检查是很严格的,所以我们平操作时,也往往很小心。
如题: (T[]) new Object[size],这种写法是一般我们是不会干的!但是有点经验的同学,还是会遇到这样写的。那么,今天咱们就来看看,像这样的写法对不对,也顺便深入理解java的类型转换机制吧!
问题1: 如题 (T[]) new Object[size] 的写法对不对?
答案是肯定的,没毛病。
为啥呢? 因为 java 的泛型只是语法糖,在java编译后,就不见了,到最后都会转为 object 类型的中间类型,所以,没毛病!
问题2: 如题所示的变量,能直接使用吗?
答案待定。不过,我们写的代码应该是不会有什么问题了!如下:
MyObjClz[] clzArr = getT(); // 直接获取变量,编译不报错
然后,由于看起来没毛病,我们就可以坑哧坑哧写后续代码了!
然而事实证明,这是错的!为啥呢? 你应该知道了,这里有类型转换错误!
好吧,从这里我们得到一个教训,正向没问题的东西,不代表反向也没问题!
既然整个数组获取回来,会发生类型转换错误,那么我们可以想办法避开这个问题,比如我一个元素一个元素的获取,应该就没问题了吧。因为我们明确知道内部元素的具体类型,而我们只是做了一个 object 的中间转换而已,所以理论正确。比如:
MyObjClz clz1 = getT()[0]; // 我只获取第一个就行了,因为 整个数组转换已经不OK
嗯,IDE还是不会报错的,我们又可以坑哧坑哧写代码了。
糟糕,运行还是异常了!哎,既然都会导致报错,那为嘛要搞这种语法呢?让我们继续看!
问题3:我们到底怎样才可以使用如题创建的变量?
其实和我们上面最后一个解题思路是一致的,整个数组类型转换是不可能了,那就单个转呗!不过,这个单个是要从源头开始。即示例如下:
MyObjClz clz1 = getTOne(i); // 直接让方法返回 单个元素
如上,运行妥妥的,我们终于可以安心睡觉了。但是为啥呢?让我们继续!
问题4:如题所示的语法到底有啥用?
额,还是很有用的!比如: ArrayList<E>, ArrayQueue<T>, 等等,里面所支持的泛型,最终都会使用到Object 来进行变量保存的,因为既然是泛型,也就是说,在写代码的时候,是不会知道变量类型的,不知道类型自然是保存不了变量的。所以必须使用 Object[] !
下面来看个应用的例子(可以想像为一个栈队列):
public class ObjectCastToAnother {
public static void main(String[] args) { ArrayAGeneric<User> arrayAGeneric = new ArrayAGeneric<>();
arrayAGeneric.push(new User());
arrayAGeneric.push(new User());
// 正确的使用姿势,返回一个元素,直接使用
User us1 = arrayAGeneric.pop();
System.out.println("us1: " + us1);
// 如下是反而教材,这句是会报错的
User us2 = arrayAGeneric.getQueue()[0];
System.out.println("us2: " + us2);
}
} class ArrayAGeneric<T> {
private T[] queue;
private int tail = 0;
public ArrayAGeneric() {
System.out.println("gen ok");
queue = newArray(10);
} private T[] newArray(int size) {
System.out.println("new array T[]");
return (T[]) new Object[size];
} public void push(T u) {
queue[tail++] = u;
} public T pop() {
return queue[--tail];
} public T[] getQueue() {
return queue;
}
}
例子一看就懂,就是一个简单的 插入元素,获取元素,使用而已。但是我们的目的是来分析,为什么两种简单的使用,一个会报错,而另一个不会报错,以及 (T[]) new Object[x]为啥不会报错!即如下:
User us = arrayAGeneric.pop(); // 正确
User us2 = arrayAGeneric.getQueue()[0]; // 错误
return (T[]) new Object[size]; // 什么操作?
看起来差距只在是由谁来取元素的问题了!那么,到底是不是这样呢?(java理论书上肯定有确切的答案)
那我们换个思路来看问题,然后java代码看不出差别,那么,我们是不是可以换成另一种方式来查看呢?是的,class字节码文件。
反编译一下,会得到两个文件:
javap -verbose -p ObjectCastToAnnother.class # 反编译class
1. main 文件
Classfile /D:/www/java/target/classes/com/xxx/tester/ObjectCastToAnnother.class
Last modified 2018-11-18; size 1398 bytes
MD5 checksum 8a1815ea41426d67e1a4b68bed4ca914
Compiled from "ObjectCastToAnnother.java"
public class com.xxx.tester.ObjectCastToAnnother
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #20.#41 // java/lang/Object."<init>":()V
#2 = Class #42 // com/xxx/tester/ArrayAGeneric
#3 = Methodref #2.#41 // com/xxx/tester/ArrayAGeneric."<init>":()V
#4 = Class #43 // com/xxx/pojo/user/User
#5 = Methodref #4.#41 // com/xxx/pojo/user/User."<init>":()V
#6 = Methodref #2.#44 // com/xxx/tester/ArrayAGeneric.push:(Ljava/lang/Object;)V
#7 = Methodref #2.#45 // com/xxx/tester/ArrayAGeneric.pop:()Ljava/lang/Object;
#8 = Fieldref #46.#47 // java/lang/System.out:Ljava/io/PrintStream;
#9 = Class #48 // java/lang/StringBuilder
#10 = Methodref #9.#41 // java/lang/StringBuilder."<init>":()V
#11 = String #49 // us1:
#12 = Methodref #9.#50 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#13 = Methodref #9.#51 // java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
#14 = Methodref #9.#52 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#15 = Methodref #53.#54 // java/io/PrintStream.println:(Ljava/lang/String;)V
#16 = Methodref #2.#55 // com/xxx/tester/ArrayAGeneric.getQueue:()[Ljava/lang/Object;
#17 = Class #56 // "[Lcom/xxx/pojo/user/User;"
#18 = String #57 // us2:
#19 = Class #58 // com/xxx/tester/ObjectCastToAnnother
#20 = Class #59 // java/lang/Object
#21 = Utf8 <init>
#22 = Utf8 ()V
#23 = Utf8 Code
#24 = Utf8 LineNumberTable
#25 = Utf8 LocalVariableTable
#26 = Utf8 this
#27 = Utf8 Lcom/xxx/tester/ObjectCastToAnnother;
#28 = Utf8 main
#29 = Utf8 ([Ljava/lang/String;)V
#30 = Utf8 args
#31 = Utf8 [Ljava/lang/String;
#32 = Utf8 arrayAGeneric
#33 = Utf8 Lcom/xxx/tester/ArrayAGeneric;
#34 = Utf8 us
#35 = Utf8 Lcom/xxx/pojo/user/User;
#36 = Utf8 us2
#37 = Utf8 LocalVariableTypeTable
#38 = Utf8 Lcom/xxx/tester/ArrayAGeneric<Lcom/xxx/pojo/user/User;>;
#39 = Utf8 SourceFile
#40 = Utf8 ObjectCastToAnnother.java
#41 = NameAndType #21:#22 // "<init>":()V
#42 = Utf8 com/xxx/tester/ArrayAGeneric
#43 = Utf8 com/xxx/pojo/user/User
#44 = NameAndType #60:#61 // push:(Ljava/lang/Object;)V
#45 = NameAndType #62:#63 // pop:()Ljava/lang/Object;
#46 = Class #64 // java/lang/System
#47 = NameAndType #65:#66 // out:Ljava/io/PrintStream;
#48 = Utf8 java/lang/StringBuilder
#49 = Utf8 us1:
#50 = NameAndType #67:#68 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#51 = NameAndType #67:#69 // append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
#52 = NameAndType #70:#71 // toString:()Ljava/lang/String;
#53 = Class #72 // java/io/PrintStream
#54 = NameAndType #73:#74 // println:(Ljava/lang/String;)V
#55 = NameAndType #75:#76 // getQueue:()[Ljava/lang/Object;
#56 = Utf8 [Lcom/xxx/pojo/user/User;
#57 = Utf8 us2:
#58 = Utf8 com/xxx/tester/ObjectCastToAnnother
#59 = Utf8 java/lang/Object
#60 = Utf8 push
#61 = Utf8 (Ljava/lang/Object;)V
#62 = Utf8 pop
#63 = Utf8 ()Ljava/lang/Object;
#64 = Utf8 java/lang/System
#65 = Utf8 out
#66 = Utf8 Ljava/io/PrintStream;
#67 = Utf8 append
#68 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#69 = Utf8 (Ljava/lang/Object;)Ljava/lang/StringBuilder;
#70 = Utf8 toString
#71 = Utf8 ()Ljava/lang/String;
#72 = Utf8 java/io/PrintStream
#73 = Utf8 println
#74 = Utf8 (Ljava/lang/String;)V
#75 = Utf8 getQueue
#76 = Utf8 ()[Ljava/lang/Object;
{
public com.xxx.tester.ObjectCastToAnnother();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 8: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/xxx/tester/ObjectCastToAnnother; public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=4, args_size=1
0: new #2 // class com/xxx/tester/ArrayAGeneric
3: dup
4: invokespecial #3 // Method com/xxx/tester/ArrayAGeneric."<init>":()V
7: astore_1
8: aload_1
9: new #4 // class com/xxx/pojo/user/User
12: dup
13: invokespecial #5 // Method com/xxx/pojo/user/User."<init>":()V
16: invokevirtual #6 // Method com/xxx/tester/ArrayAGeneric.push:(Ljava/lang/Object;)V
19: aload_1
20: new #4 // class com/xxx/pojo/user/User
23: dup
24: invokespecial #5 // Method com/xxx/pojo/user/User."<init>":()V
27: invokevirtual #6 // Method com/xxx/tester/ArrayAGeneric.push:(Ljava/lang/Object;)V
30: aload_1
31: invokevirtual #7 // Method com/xxx/tester/ArrayAGeneric.pop:()Ljava/lang/Object;
34: checkcast #4 // class com/xxx/pojo/user/User
37: astore_2
38: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
41: new #9 // class java/lang/StringBuilder
44: dup
45: invokespecial #10 // Method java/lang/StringBuilder."<init>":()V
48: ldc #11 // String us1:
50: invokevirtual #12 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
53: aload_2
54: invokevirtual #13 // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
57: invokevirtual #14 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
60: invokevirtual #15 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
63: aload_1
64: invokevirtual #16 // Method com/xxx/tester/ArrayAGeneric.getQueue:()[Ljava/lang/Object;
67: checkcast #17 // class "[Lcom/xxx/pojo/user/User;"
70: iconst_0
71: aaload
72: astore_3
73: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
76: new #9 // class java/lang/StringBuilder
79: dup
80: invokespecial #10 // Method java/lang/StringBuilder."<init>":()V
83: ldc #18 // String us2:
85: invokevirtual #12 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
88: aload_3
89: invokevirtual #13 // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
92: invokevirtual #14 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
95: invokevirtual #15 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
98: return
LineNumberTable:
line 11: 0
line 12: 8
line 13: 19
line 14: 30
line 15: 38
line 16: 63
line 17: 73
line 18: 98
LocalVariableTable:
Start Length Slot Name Signature
0 99 0 args [Ljava/lang/String;
8 91 1 arrayAGeneric Lcom/xxx/tester/ArrayAGeneric;
38 61 2 us Lcom/xxx/pojo/user/User;
73 26 3 us2 Lcom/xxx/pojo/user/User;
LocalVariableTypeTable:
Start Length Slot Name Signature
8 91 1 arrayAGeneric Lcom/xxx/tester/ArrayAGeneric<Lcom/xxx/pojo/user/User;>;
}
SourceFile: "ObjectCastToAnnother.java"
2. ArrayAGeneric 文件
Classfile /D:/www/java/target/classes/com/xxx/tester/ArrayAGeneric.class
Last modified 2018-11-18; size 1390 bytes
MD5 checksum fc9f7f9311bf542d9f1b03e39e32aba8
Compiled from "ObjectCastToAnnother.java"
class com.xxx.tester.ArrayAGeneric<T extends java.lang.Object> extends java.lang.Object
minor version: 0
major version: 52
flags: ACC_SUPER
Constant pool:
#1 = Methodref #9.#46 // java/lang/Object."<init>":()V
#2 = Fieldref #11.#47 // com/xxx/tester/ArrayAGeneric.tail:I
#3 = Fieldref #48.#49 // java/lang/System.out:Ljava/io/PrintStream;
#4 = String #50 // gen ok
#5 = Methodref #51.#52 // java/io/PrintStream.println:(Ljava/lang/String;)V
#6 = Methodref #11.#53 // com/xxx/tester/ArrayAGeneric.newArray:(I)[Ljava/lang/Object;
#7 = Fieldref #11.#54 // com/xxx/tester/ArrayAGeneric.queue:[Ljava/lang/Object;
#8 = String #55 // new array T[]
#9 = Class #56 // java/lang/Object
#10 = Class #13 // "[Ljava/lang/Object;"
#11 = Class #57 // com/xxx/tester/ArrayAGeneric
#12 = Utf8 queue
#13 = Utf8 [Ljava/lang/Object;
#14 = Utf8 Signature
#15 = Utf8 [TT;
#16 = Utf8 tail
#17 = Utf8 I
#18 = Utf8 <init>
#19 = Utf8 ()V
#20 = Utf8 Code
#21 = Utf8 LineNumberTable
#22 = Utf8 LocalVariableTable
#23 = Utf8 this
#24 = Utf8 Lcom/xxx/tester/ArrayAGeneric;
#25 = Utf8 LocalVariableTypeTable
#26 = Utf8 Lcom/xxx/tester/ArrayAGeneric<TT;>;
#27 = Utf8 newArray
#28 = Utf8 (I)[Ljava/lang/Object;
#29 = Utf8 size
#30 = Utf8 (I)[TT;
#31 = Utf8 push
#32 = Utf8 (Ljava/lang/Object;)V
#33 = Utf8 u
#34 = Utf8 Ljava/lang/Object;
#35 = Utf8 TT;
#36 = Utf8 (TT;)V
#37 = Utf8 pop
#38 = Utf8 ()Ljava/lang/Object;
#39 = Utf8 ()TT;
#40 = Utf8 getQueue
#41 = Utf8 ()[Ljava/lang/Object;
#42 = Utf8 ()[TT;
#43 = Utf8 <T:Ljava/lang/Object;>Ljava/lang/Object;
#44 = Utf8 SourceFile
#45 = Utf8 ObjectCastToAnnother.java
#46 = NameAndType #18:#19 // "<init>":()V
#47 = NameAndType #16:#17 // tail:I
#48 = Class #58 // java/lang/System
#49 = NameAndType #59:#60 // out:Ljava/io/PrintStream;
#50 = Utf8 gen ok
#51 = Class #61 // java/io/PrintStream
#52 = NameAndType #62:#63 // println:(Ljava/lang/String;)V
#53 = NameAndType #27:#28 // newArray:(I)[Ljava/lang/Object;
#54 = NameAndType #12:#13 // queue:[Ljava/lang/Object;
#55 = Utf8 new array T[]
#56 = Utf8 java/lang/Object
#57 = Utf8 com/xxx/tester/ArrayAGeneric
#58 = Utf8 java/lang/System
#59 = Utf8 out
#60 = Utf8 Ljava/io/PrintStream;
#61 = Utf8 java/io/PrintStream
#62 = Utf8 println
#63 = Utf8 (Ljava/lang/String;)V
{
private T[] queue;
descriptor: [Ljava/lang/Object;
flags: ACC_PRIVATE
Signature: #15 // [TT; private int tail;
descriptor: I
flags: ACC_PRIVATE public com.xxx.tester.ArrayAGeneric();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_0
6: putfield #2 // Field tail:I
9: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
12: ldc #4 // String gen ok
14: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
17: aload_0
18: aload_0
19: bipush 10
21: invokespecial #6 // Method newArray:(I)[Ljava/lang/Object;
24: putfield #7 // Field queue:[Ljava/lang/Object;
27: return
LineNumberTable:
line 24: 0
line 23: 4
line 25: 9
line 26: 17
line 27: 27
LocalVariableTable:
Start Length Slot Name Signature
0 28 0 this Lcom/xxx/tester/ArrayAGeneric;
LocalVariableTypeTable:
Start Length Slot Name Signature
0 28 0 this Lcom/xxx/tester/ArrayAGeneric<TT;>; private T[] newArray(int);
descriptor: (I)[Ljava/lang/Object;
flags: ACC_PRIVATE
Code:
stack=2, locals=2, args_size=2
0: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #8 // String new array T[]
5: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: iload_1
9: anewarray #9 // class java/lang/Object
12: checkcast #10 // class "[Ljava/lang/Object;"
15: areturn
LineNumberTable:
line 30: 0
line 31: 8
LocalVariableTable:
Start Length Slot Name Signature
0 16 0 this Lcom/xxx/tester/ArrayAGeneric;
0 16 1 size I
LocalVariableTypeTable:
Start Length Slot Name Signature
0 16 0 this Lcom/xxx/tester/ArrayAGeneric<TT;>;
Signature: #30 // (I)[TT; public void push(T);
descriptor: (Ljava/lang/Object;)V
flags: ACC_PUBLIC
Code:
stack=5, locals=2, args_size=2
0: aload_0
1: getfield #7 // Field queue:[Ljava/lang/Object;
4: aload_0
5: dup
6: getfield #2 // Field tail:I
9: dup_x1
10: iconst_1
11: iadd
12: putfield #2 // Field tail:I
15: aload_1
16: aastore
17: return
LineNumberTable:
line 35: 0
line 36: 17
LocalVariableTable:
Start Length Slot Name Signature
0 18 0 this Lcom/xxx/tester/ArrayAGeneric;
0 18 1 u Ljava/lang/Object;
LocalVariableTypeTable:
Start Length Slot Name Signature
0 18 0 this Lcom/xxx/tester/ArrayAGeneric<TT;>;
0 18 1 u TT;
Signature: #36 // (TT;)V public T pop();
descriptor: ()Ljava/lang/Object;
flags: ACC_PUBLIC
Code:
stack=4, locals=1, args_size=1
0: aload_0
1: getfield #7 // Field queue:[Ljava/lang/Object;
4: aload_0
5: dup
6: getfield #2 // Field tail:I
9: iconst_1
10: isub
11: dup_x1
12: putfield #2 // Field tail:I
15: aaload
16: areturn
LineNumberTable:
line 39: 0
LocalVariableTable:
Start Length Slot Name Signature
0 17 0 this Lcom/xxx/tester/ArrayAGeneric;
LocalVariableTypeTable:
Start Length Slot Name Signature
0 17 0 this Lcom/xxx/tester/ArrayAGeneric<TT;>;
Signature: #39 // ()TT; public T[] getQueue();
descriptor: ()[Ljava/lang/Object;
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #7 // Field queue:[Ljava/lang/Object;
4: areturn
LineNumberTable:
line 43: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/xxx/tester/ArrayAGeneric;
LocalVariableTypeTable:
Start Length Slot Name Signature
0 5 0 this Lcom/xxx/tester/ArrayAGeneric<TT;>;
Signature: #42 // ()[TT;
}
Signature: #43 // <T:Ljava/lang/Object;>Ljava/lang/Object;
SourceFile: "ObjectCastToAnnother.java"
其实从 main 文件中已经看出端倪,第120~123行,即 us1 赋值的地方:
30: aload_1
31: invokevirtual #7 // Method com/xxx/tester/ArrayAGeneric.pop:()Ljava/lang/Object;
34: checkcast #4 // class com/xxx/pojo/user/User
37: astore_2
这里看到,有一个 checkcast 的指令,即是进行类型转换检查,而本身的 pop() 后的元素类型一致,因此运行OK!
我们来看下一取值方式,第134~138行,即 us2 赋值的地方:
63: aload_1
64: invokevirtual #16 // Method com/xxx/tester/ArrayAGeneric.getQueue:()[Ljava/lang/Object;
67: checkcast #17 // class "[Lcom/xxx/pojo/user/User;"
70: iconst_0
71: aaload
看到了吧,关键的地方: checkcast class "[Lcom/xxx/pojo/user/User", 即将获取到的值进行 数组类型的转换检查,如此检查,自然是通不过的了。所以,理解了吧,是因为,数组元素的获取顺序为先进行类型转换,然后再获取元素值!
现在,还剩下一个问题: 为什么通过 getOne() 的形式,代码就是可行的呢?
这个问题的答案,在 ArrayAGeneric 的文件中,可以轻松找到答案:
ArrayAGeneric 文件,第 174~184行:
0: aload_0
1: getfield #7 // Field queue:[Ljava/lang/Object;
4: aload_0
5: dup
6: getfield #2 // Field tail:I
9: iconst_1
10: isub
11: dup_x1
12: putfield #2 // Field tail:I
15: aaload
16: areturn
可以看出来,这里就只是一个数组元素的获取过程,返回类型为 Object, 而此 Object 的原始类型即是泛型指定的。因此,在外部进行转换自然也不会错!
好了,到此,疑问已经得到回答。是类型转换的检查时机导致了我们的代码错误。
另外,我们还可以继续看一下 newArray() 的代码:
0: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #8 // String new array T[]
5: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: iload_1
9: anewarray #9 // class java/lang/Object
12: checkcast #10 // class "[Ljava/lang/Object;"
15: areturn
这里也可以明显的看出, T[] 其实就是 Object[] 。
数组类型的强转要特别注意,其向上转型是ok的,向下转型则是不被允许的,因为继承的关系,父类不能保证所有的所有的元素都能强制转换(元素可以是指定类型的任意子类),所以干脆杜绝了所有的向下转型了!(我猜应该是出于性能的考虑)
不要以为ArrayList<T>中的T是指的具体泛型类,完。