1. 程式人生 > >Java陣列型別轉換

Java陣列型別轉換

在做專案的過程中,遇到一個很奇怪的問題。為了說明清楚,先舉個栗子:

public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(2);
        Integer[] a1 = list.toArray(new Integer[list.size()]);//(1)
        Integer[] a2 = (Integer[]) list.toArray();//(2)
}

咋一看,會覺得上面的程式碼沒有問題。實際情況是程式碼(1)處是沒有問題的,而(2)處會發生ClassCastException;list明明泛型中指定了裡面引數的型別,為何還是會發生型別轉換的錯誤?我們知道Java泛型只是編譯器有效,執行期泛型是要被擦除的。

其實這個問題解釋起來也不復雜,我們知道Java一切皆物件,那麼陣列也必然為陣列物件了。我們加些列印語句:

public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(2);
        Integer[] a1 = new Integer[1];//(1)
        Integer[] a2 = new Integer[1];
        System.out.println("a1物件型別: " + a1.getClass());
        System.out.println("a2物件型別: " + a2.getClass());
        System.out.println("list.toArray(new Integer[list.size()])物件型別: "
                             + list.toArray(new Integer[list.size()]).getClass());
        System.out.println("list.toArray()物件型別: " + list.toArray().getClass());
        a1 = list.toArray(new Integer[list.size()]);
        a2 = (Integer[]) list.toArray();//(2)
}

看看執行效果


看見沒有a2與list.toArray()根本不是一種型別。執行時list.toArray()物件也不是Integer[]型別的例項。為了驗證該說話繼續新增程式碼:
System.out.println(list.toArray() instanceof  Integer[]);
輸出結果為false. 現在在設計一個小實驗,我們修改型別轉化之後的陣列:
public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        Object[] a1 = list.toArray();//(1)
        Object[] a2 = list.toArray(new Integer[list.size()]);//(2)
        System.out.println("a1物件型別" + a1.getClass());
        System.out.println("a2物件型別" + a2.getClass());
        //修改元素
        a1[0] = new BigDecimal(5);//(3)
        a2[0] = new BigDecimal(5);//(4)
}
執行結果
我們知道BigDecimal和Integer類已經沒有半毛錢的關係了,(3)執行成功,(4)丟擲異常。說明list.toArray()之後,該陣列可以存放任何物件均是合法的,所以將這樣的陣列物件轉型為Integer[]合法的話那Java型別轉化就亂套了。 現在想想為何list.toArray(T[])為何能執行成功? 看下ArrayList.java原始碼:
    @SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }
起作用的為 System.arraycopy(elementData, 0, a, 0, size),這裡直接返回的陣列物件a是帶有泛型的;這其實為native方法,和 Java無關了! Debug執行看下效果:
換了角度理解我們知道,list.toArray(T[])引數帶有泛型,就算我們自己可以輕易實現列表向陣列轉換。我們在看看list.toArray()在Debug模式下執行效果:
雖然這裡的泛型型別已經丟了,陣列中儲存的物件型別沒有問題,但是返回的陣列泛型已經丟了。 陣列物件繼續思考: 1.Integer[] 型別的陣列物件是否也是Object[]的例項? 2. 宣告Object[]型別的陣列物件,執行期間一定為Object[]型別嗎?若宣告之後直接全部例項化為StringBuilder物件呢?
如:
    public static void main(String[] args) {
        Integer[] a1 = new Integer[1];
        System.out.println("a1 instanceof Integer[]:\t" + (a1 instanceof Integer[]));
        System.out.println("a1 instanceof Object[]:\t"+ (a1 instanceof Object[]));
        Object[] a2 = {new StringBuilder("abc"),new StringBuilder("bcd")};//只是例項化,並不返回型別
        //System.out.println({new StringBuilder("abc"),new StringBuilder("bcd")});//語法錯誤
        System.out.println(a2.getClass());
        Object[] a3 = a1;
        System.out.println(a3.getClass());
        Class c1 = Integer[].class;
        Class c2 = Object[].class;
        System.out.println("Integer[]是否為Object[]子型別:" + c2.isAssignableFrom(c1));
    }
執行結果為:
從中我們可以得出下面的結論: 1.陣列型別也存在子父類(或介面)之間關係 2.變數宣告並不能決定其最終型別。 3.陣列初始化{}本身不具備型別。