從位元組碼看java型別轉換【 深入理解 (T[]) new Object[size] 】

weixin_34119545發表於2018-11-18

  我們都知道,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[] !

下面來看個應用的例子(可以想像為一個棧佇列):

 1 public class ObjectCastToAnother {
 2     public static void main(String[] args) {
 3 
 4         ArrayAGeneric<User> arrayAGeneric = new ArrayAGeneric<>();
 5         arrayAGeneric.push(new User());
 6         arrayAGeneric.push(new User());
 7         // 正確的使用姿勢,返回一個元素,直接使用
 8         User us1 = arrayAGeneric.pop();
 9         System.out.println("us1: " + us1);
10         // 如下是反而教材,這句是會報錯的
11         User us2 = arrayAGeneric.getQueue()[0];
12         System.out.println("us2: " + us2);
13     }
14 }
15 
16 class ArrayAGeneric<T> {
17     private T[] queue;
18     private int tail = 0;
19     public ArrayAGeneric() {
20         System.out.println("gen ok");
21         queue = newArray(10);
22     }
23 
24     private T[] newArray(int size) {
25         System.out.println("new array T[]");
26         return (T[]) new Object[size];
27     }
28 
29     public void push(T u) {
30         queue[tail++] = u;
31     }
32 
33     public T pop() {
34         return queue[--tail];
35     }
36 
37     public T[] getQueue() {
38         return queue;
39     }
40 }

  例子一看就懂,就是一個簡單的 插入元素,獲取元素,使用而已。但是我們的目的是來分析,為什麼兩種簡單的使用,一個會報錯,而另一個不會報錯,以及 (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 檔案

  1 Classfile /D:/www/java/target/classes/com/xxx/tester/ObjectCastToAnnother.class
  2   Last modified 2018-11-18; size 1398 bytes
  3   MD5 checksum 8a1815ea41426d67e1a4b68bed4ca914
  4   Compiled from "ObjectCastToAnnother.java"
  5 public class com.xxx.tester.ObjectCastToAnnother
  6   minor version: 0
  7   major version: 52
  8   flags: ACC_PUBLIC, ACC_SUPER
  9 Constant pool:
 10    #1 = Methodref          #20.#41        // java/lang/Object."<init>":()V
 11    #2 = Class              #42            // com/xxx/tester/ArrayAGeneric
 12    #3 = Methodref          #2.#41         // com/xxx/tester/ArrayAGeneric."<init>":()V
 13    #4 = Class              #43            // com/xxx/pojo/user/User
 14    #5 = Methodref          #4.#41         // com/xxx/pojo/user/User."<init>":()V
 15    #6 = Methodref          #2.#44         // com/xxx/tester/ArrayAGeneric.push:(Ljava/lang/Object;)V
 16    #7 = Methodref          #2.#45         // com/xxx/tester/ArrayAGeneric.pop:()Ljava/lang/Object;
 17    #8 = Fieldref           #46.#47        // java/lang/System.out:Ljava/io/PrintStream;
 18    #9 = Class              #48            // java/lang/StringBuilder
 19   #10 = Methodref          #9.#41         // java/lang/StringBuilder."<init>":()V
 20   #11 = String             #49            // us1:
 21   #12 = Methodref          #9.#50         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
 22   #13 = Methodref          #9.#51         // java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
 23   #14 = Methodref          #9.#52         // java/lang/StringBuilder.toString:()Ljava/lang/String;
 24   #15 = Methodref          #53.#54        // java/io/PrintStream.println:(Ljava/lang/String;)V
 25   #16 = Methodref          #2.#55         // com/xxx/tester/ArrayAGeneric.getQueue:()[Ljava/lang/Object;
 26   #17 = Class              #56            // "[Lcom/xxx/pojo/user/User;"
 27   #18 = String             #57            // us2:
 28   #19 = Class              #58            // com/xxx/tester/ObjectCastToAnnother
 29   #20 = Class              #59            // java/lang/Object
 30   #21 = Utf8               <init>
 31   #22 = Utf8               ()V
 32   #23 = Utf8               Code
 33   #24 = Utf8               LineNumberTable
 34   #25 = Utf8               LocalVariableTable
 35   #26 = Utf8               this
 36   #27 = Utf8               Lcom/xxx/tester/ObjectCastToAnnother;
 37   #28 = Utf8               main
 38   #29 = Utf8               ([Ljava/lang/String;)V
 39   #30 = Utf8               args
 40   #31 = Utf8               [Ljava/lang/String;
 41   #32 = Utf8               arrayAGeneric
 42   #33 = Utf8               Lcom/xxx/tester/ArrayAGeneric;
 43   #34 = Utf8               us
 44   #35 = Utf8               Lcom/xxx/pojo/user/User;
 45   #36 = Utf8               us2
 46   #37 = Utf8               LocalVariableTypeTable
 47   #38 = Utf8               Lcom/xxx/tester/ArrayAGeneric<Lcom/xxx/pojo/user/User;>;
 48   #39 = Utf8               SourceFile
 49   #40 = Utf8               ObjectCastToAnnother.java
 50   #41 = NameAndType        #21:#22        // "<init>":()V
 51   #42 = Utf8               com/xxx/tester/ArrayAGeneric
 52   #43 = Utf8               com/xxx/pojo/user/User
 53   #44 = NameAndType        #60:#61        // push:(Ljava/lang/Object;)V
 54   #45 = NameAndType        #62:#63        // pop:()Ljava/lang/Object;
 55   #46 = Class              #64            // java/lang/System
 56   #47 = NameAndType        #65:#66        // out:Ljava/io/PrintStream;
 57   #48 = Utf8               java/lang/StringBuilder
 58   #49 = Utf8               us1:
 59   #50 = NameAndType        #67:#68        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
 60   #51 = NameAndType        #67:#69        // append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
 61   #52 = NameAndType        #70:#71        // toString:()Ljava/lang/String;
 62   #53 = Class              #72            // java/io/PrintStream
 63   #54 = NameAndType        #73:#74        // println:(Ljava/lang/String;)V
 64   #55 = NameAndType        #75:#76        // getQueue:()[Ljava/lang/Object;
 65   #56 = Utf8               [Lcom/xxx/pojo/user/User;
 66   #57 = Utf8               us2:
 67   #58 = Utf8               com/xxx/tester/ObjectCastToAnnother
 68   #59 = Utf8               java/lang/Object
 69   #60 = Utf8               push
 70   #61 = Utf8               (Ljava/lang/Object;)V
 71   #62 = Utf8               pop
 72   #63 = Utf8               ()Ljava/lang/Object;
 73   #64 = Utf8               java/lang/System
 74   #65 = Utf8               out
 75   #66 = Utf8               Ljava/io/PrintStream;
 76   #67 = Utf8               append
 77   #68 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
 78   #69 = Utf8               (Ljava/lang/Object;)Ljava/lang/StringBuilder;
 79   #70 = Utf8               toString
 80   #71 = Utf8               ()Ljava/lang/String;
 81   #72 = Utf8               java/io/PrintStream
 82   #73 = Utf8               println
 83   #74 = Utf8               (Ljava/lang/String;)V
 84   #75 = Utf8               getQueue
 85   #76 = Utf8               ()[Ljava/lang/Object;
 86 {
 87   public com.xxx.tester.ObjectCastToAnnother();
 88     descriptor: ()V
 89     flags: ACC_PUBLIC
 90     Code:
 91       stack=1, locals=1, args_size=1
 92          0: aload_0
 93          1: invokespecial #1                  // Method java/lang/Object."<init>":()V
 94          4: return
 95       LineNumberTable:
 96         line 8: 0
 97       LocalVariableTable:
 98         Start  Length  Slot  Name   Signature
 99             0       5     0  this   Lcom/xxx/tester/ObjectCastToAnnother;
100 
101   public static void main(java.lang.String[]);
102     descriptor: ([Ljava/lang/String;)V
103     flags: ACC_PUBLIC, ACC_STATIC
104     Code:
105       stack=3, locals=4, args_size=1
106          0: new           #2                  // class com/xxx/tester/ArrayAGeneric
107          3: dup
108          4: invokespecial #3                  // Method com/xxx/tester/ArrayAGeneric."<init>":()V
109          7: astore_1
110          8: aload_1
111          9: new           #4                  // class com/xxx/pojo/user/User
112         12: dup
113         13: invokespecial #5                  // Method com/xxx/pojo/user/User."<init>":()V
114         16: invokevirtual #6                  // Method com/xxx/tester/ArrayAGeneric.push:(Ljava/lang/Object;)V
115         19: aload_1
116         20: new           #4                  // class com/xxx/pojo/user/User
117         23: dup
118         24: invokespecial #5                  // Method com/xxx/pojo/user/User."<init>":()V
119         27: invokevirtual #6                  // Method com/xxx/tester/ArrayAGeneric.push:(Ljava/lang/Object;)V
120         30: aload_1
121         31: invokevirtual #7                  // Method com/xxx/tester/ArrayAGeneric.pop:()Ljava/lang/Object;
122         34: checkcast     #4                  // class com/xxx/pojo/user/User
123         37: astore_2
124         38: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
125         41: new           #9                  // class java/lang/StringBuilder
126         44: dup
127         45: invokespecial #10                 // Method java/lang/StringBuilder."<init>":()V
128         48: ldc           #11                 // String us1:
129         50: invokevirtual #12                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
130         53: aload_2
131         54: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
132         57: invokevirtual #14                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
133         60: invokevirtual #15                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
134         63: aload_1
135         64: invokevirtual #16                 // Method com/xxx/tester/ArrayAGeneric.getQueue:()[Ljava/lang/Object;
136         67: checkcast     #17                 // class "[Lcom/xxx/pojo/user/User;"
137         70: iconst_0
138         71: aaload
139         72: astore_3
140         73: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
141         76: new           #9                  // class java/lang/StringBuilder
142         79: dup
143         80: invokespecial #10                 // Method java/lang/StringBuilder."<init>":()V
144         83: ldc           #18                 // String us2:
145         85: invokevirtual #12                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
146         88: aload_3
147         89: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
148         92: invokevirtual #14                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
149         95: invokevirtual #15                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
150         98: return
151       LineNumberTable:
152         line 11: 0
153         line 12: 8
154         line 13: 19
155         line 14: 30
156         line 15: 38
157         line 16: 63
158         line 17: 73
159         line 18: 98
160       LocalVariableTable:
161         Start  Length  Slot  Name   Signature
162             0      99     0  args   [Ljava/lang/String;
163             8      91     1 arrayAGeneric   Lcom/xxx/tester/ArrayAGeneric;
164            38      61     2    us   Lcom/xxx/pojo/user/User;
165            73      26     3   us2   Lcom/xxx/pojo/user/User;
166       LocalVariableTypeTable:
167         Start  Length  Slot  Name   Signature
168             8      91     1 arrayAGeneric   Lcom/xxx/tester/ArrayAGeneric<Lcom/xxx/pojo/user/User;>;
169 }
170 SourceFile: "ObjectCastToAnnother.java"

 2. ArrayAGeneric 檔案

  1 Classfile /D:/www/java/target/classes/com/xxx/tester/ArrayAGeneric.class
  2   Last modified 2018-11-18; size 1390 bytes
  3   MD5 checksum fc9f7f9311bf542d9f1b03e39e32aba8
  4   Compiled from "ObjectCastToAnnother.java"
  5 class com.xxx.tester.ArrayAGeneric<T extends java.lang.Object> extends java.lang.Object
  6   minor version: 0
  7   major version: 52
  8   flags: ACC_SUPER
  9 Constant pool:
 10    #1 = Methodref          #9.#46         // java/lang/Object."<init>":()V
 11    #2 = Fieldref           #11.#47        // com/xxx/tester/ArrayAGeneric.tail:I
 12    #3 = Fieldref           #48.#49        // java/lang/System.out:Ljava/io/PrintStream;
 13    #4 = String             #50            // gen ok
 14    #5 = Methodref          #51.#52        // java/io/PrintStream.println:(Ljava/lang/String;)V
 15    #6 = Methodref          #11.#53        // com/xxx/tester/ArrayAGeneric.newArray:(I)[Ljava/lang/Object;
 16    #7 = Fieldref           #11.#54        // com/xxx/tester/ArrayAGeneric.queue:[Ljava/lang/Object;
 17    #8 = String             #55            // new array T[]
 18    #9 = Class              #56            // java/lang/Object
 19   #10 = Class              #13            // "[Ljava/lang/Object;"
 20   #11 = Class              #57            // com/xxx/tester/ArrayAGeneric
 21   #12 = Utf8               queue
 22   #13 = Utf8               [Ljava/lang/Object;
 23   #14 = Utf8               Signature
 24   #15 = Utf8               [TT;
 25   #16 = Utf8               tail
 26   #17 = Utf8               I
 27   #18 = Utf8               <init>
 28   #19 = Utf8               ()V
 29   #20 = Utf8               Code
 30   #21 = Utf8               LineNumberTable
 31   #22 = Utf8               LocalVariableTable
 32   #23 = Utf8               this
 33   #24 = Utf8               Lcom/xxx/tester/ArrayAGeneric;
 34   #25 = Utf8               LocalVariableTypeTable
 35   #26 = Utf8               Lcom/xxx/tester/ArrayAGeneric<TT;>;
 36   #27 = Utf8               newArray
 37   #28 = Utf8               (I)[Ljava/lang/Object;
 38   #29 = Utf8               size
 39   #30 = Utf8               (I)[TT;
 40   #31 = Utf8               push
 41   #32 = Utf8               (Ljava/lang/Object;)V
 42   #33 = Utf8               u
 43   #34 = Utf8               Ljava/lang/Object;
 44   #35 = Utf8               TT;
 45   #36 = Utf8               (TT;)V
 46   #37 = Utf8               pop
 47   #38 = Utf8               ()Ljava/lang/Object;
 48   #39 = Utf8               ()TT;
 49   #40 = Utf8               getQueue
 50   #41 = Utf8               ()[Ljava/lang/Object;
 51   #42 = Utf8               ()[TT;
 52   #43 = Utf8               <T:Ljava/lang/Object;>Ljava/lang/Object;
 53   #44 = Utf8               SourceFile
 54   #45 = Utf8               ObjectCastToAnnother.java
 55   #46 = NameAndType        #18:#19        // "<init>":()V
 56   #47 = NameAndType        #16:#17        // tail:I
 57   #48 = Class              #58            // java/lang/System
 58   #49 = NameAndType        #59:#60        // out:Ljava/io/PrintStream;
 59   #50 = Utf8               gen ok
 60   #51 = Class              #61            // java/io/PrintStream
 61   #52 = NameAndType        #62:#63        // println:(Ljava/lang/String;)V
 62   #53 = NameAndType        #27:#28        // newArray:(I)[Ljava/lang/Object;
 63   #54 = NameAndType        #12:#13        // queue:[Ljava/lang/Object;
 64   #55 = Utf8               new array T[]
 65   #56 = Utf8               java/lang/Object
 66   #57 = Utf8               com/xxx/tester/ArrayAGeneric
 67   #58 = Utf8               java/lang/System
 68   #59 = Utf8               out
 69   #60 = Utf8               Ljava/io/PrintStream;
 70   #61 = Utf8               java/io/PrintStream
 71   #62 = Utf8               println
 72   #63 = Utf8               (Ljava/lang/String;)V
 73 {
 74   private T[] queue;
 75     descriptor: [Ljava/lang/Object;
 76     flags: ACC_PRIVATE
 77     Signature: #15                          // [TT;
 78 
 79   private int tail;
 80     descriptor: I
 81     flags: ACC_PRIVATE
 82 
 83   public com.xxx.tester.ArrayAGeneric();
 84     descriptor: ()V
 85     flags: ACC_PUBLIC
 86     Code:
 87       stack=3, locals=1, args_size=1
 88          0: aload_0
 89          1: invokespecial #1                  // Method java/lang/Object."<init>":()V
 90          4: aload_0
 91          5: iconst_0
 92          6: putfield      #2                  // Field tail:I
 93          9: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
 94         12: ldc           #4                  // String gen ok
 95         14: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
 96         17: aload_0
 97         18: aload_0
 98         19: bipush        10
 99         21: invokespecial #6                  // Method newArray:(I)[Ljava/lang/Object;
100         24: putfield      #7                  // Field queue:[Ljava/lang/Object;
101         27: return
102       LineNumberTable:
103         line 24: 0
104         line 23: 4
105         line 25: 9
106         line 26: 17
107         line 27: 27
108       LocalVariableTable:
109         Start  Length  Slot  Name   Signature
110             0      28     0  this   Lcom/xxx/tester/ArrayAGeneric;
111       LocalVariableTypeTable:
112         Start  Length  Slot  Name   Signature
113             0      28     0  this   Lcom/xxx/tester/ArrayAGeneric<TT;>;
114 
115   private T[] newArray(int);
116     descriptor: (I)[Ljava/lang/Object;
117     flags: ACC_PRIVATE
118     Code:
119       stack=2, locals=2, args_size=2
120          0: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
121          3: ldc           #8                  // String new array T[]
122          5: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
123          8: iload_1
124          9: anewarray     #9                  // class java/lang/Object
125         12: checkcast     #10                 // class "[Ljava/lang/Object;"
126         15: areturn
127       LineNumberTable:
128         line 30: 0
129         line 31: 8
130       LocalVariableTable:
131         Start  Length  Slot  Name   Signature
132             0      16     0  this   Lcom/xxx/tester/ArrayAGeneric;
133             0      16     1  size   I
134       LocalVariableTypeTable:
135         Start  Length  Slot  Name   Signature
136             0      16     0  this   Lcom/xxx/tester/ArrayAGeneric<TT;>;
137     Signature: #30                          // (I)[TT;
138 
139   public void push(T);
140     descriptor: (Ljava/lang/Object;)V
141     flags: ACC_PUBLIC
142     Code:
143       stack=5, locals=2, args_size=2
144          0: aload_0
145          1: getfield      #7                  // Field queue:[Ljava/lang/Object;
146          4: aload_0
147          5: dup
148          6: getfield      #2                  // Field tail:I
149          9: dup_x1
150         10: iconst_1
151         11: iadd
152         12: putfield      #2                  // Field tail:I
153         15: aload_1
154         16: aastore
155         17: return
156       LineNumberTable:
157         line 35: 0
158         line 36: 17
159       LocalVariableTable:
160         Start  Length  Slot  Name   Signature
161             0      18     0  this   Lcom/xxx/tester/ArrayAGeneric;
162             0      18     1     u   Ljava/lang/Object;
163       LocalVariableTypeTable:
164         Start  Length  Slot  Name   Signature
165             0      18     0  this   Lcom/xxx/tester/ArrayAGeneric<TT;>;
166             0      18     1     u   TT;
167     Signature: #36                          // (TT;)V
168 
169   public T pop();
170     descriptor: ()Ljava/lang/Object;
171     flags: ACC_PUBLIC
172     Code:
173       stack=4, locals=1, args_size=1
174          0: aload_0
175          1: getfield      #7                  // Field queue:[Ljava/lang/Object;
176          4: aload_0
177          5: dup
178          6: getfield      #2                  // Field tail:I
179          9: iconst_1
180         10: isub
181         11: dup_x1
182         12: putfield      #2                  // Field tail:I
183         15: aaload
184         16: areturn
185       LineNumberTable:
186         line 39: 0
187       LocalVariableTable:
188         Start  Length  Slot  Name   Signature
189             0      17     0  this   Lcom/xxx/tester/ArrayAGeneric;
190       LocalVariableTypeTable:
191         Start  Length  Slot  Name   Signature
192             0      17     0  this   Lcom/xxx/tester/ArrayAGeneric<TT;>;
193     Signature: #39                          // ()TT;
194 
195   public T[] getQueue();
196     descriptor: ()[Ljava/lang/Object;
197     flags: ACC_PUBLIC
198     Code:
199       stack=1, locals=1, args_size=1
200          0: aload_0
201          1: getfield      #7                  // Field queue:[Ljava/lang/Object;
202          4: areturn
203       LineNumberTable:
204         line 43: 0
205       LocalVariableTable:
206         Start  Length  Slot  Name   Signature
207             0       5     0  this   Lcom/xxx/tester/ArrayAGeneric;
208       LocalVariableTypeTable:
209         Start  Length  Slot  Name   Signature
210             0       5     0  this   Lcom/xxx/tester/ArrayAGeneric<TT;>;
211     Signature: #42                          // ()[TT;
212 }
213 Signature: #43                          // <T:Ljava/lang/Object;>Ljava/lang/Object;
214 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的,向下轉型則是不被允許的,因為繼承的關係,父類不能保證所有的所有的元素都能強制轉換(元素可以是指定型別的任意子類),所以乾脆杜絕了所有的向下轉型了!(我猜應該是出於效能的考慮)

  完。

相關文章