陣列複製


瞭解陣列是物件,你就應該知道,以下這個並非陣列複製:

int[] scores1 = {88, 81, 74, 68, 78, 76, 77, 85, 95, 93};
int[] scores2 = scores1;

這個程式片段,只不過是將scores1參考的陣列物件,也給scores2參考。如果你要作陣列複製,基本作法是另行建立新陣列。例如:

int[] scores1 = {88, 81, 74, 68, 78, 76, 77, 85, 95, 93};
int[] scores2 = new int[scores1.length];
for(int i = 0; i < scores1.length; i++) {
    scores2[i] = scores1[i];
}

在這個程式片段中,建立一個長度與scores1相同的新陣列,再逐一走訪scores1每個索引元素,並指定給scores2對應的索引位置。事實上,不用自行使用迴圈作值的複製,而可以使用System.arraycopy()方法,這個方法會使用原生方式複製每個索引元素,比自行使用迴圈來得快:

int[] scores1 = {88, 81, 74, 68, 78, 76, 77, 85, 95, 93};
int[] scores2 = new int[scores1.length];
System.arraycopy(scores1, 0, scores2, 0, scores1.length);

System.arraycopy()的五個參數分別是來源陣列、來源起始索引、目的陣列、目的起始索引、複製長度。如果使用JDK6以上,還有個更方便的Arrays.copyOf()方法,你不用另行建立新陣列,Arrays.copyOf()會幫你建立。例如:

package cc.openhome;

import java.util.Arrays;

public class CopyArray {
    public static void main(String[] args) {
        int[] scores1 = {88, 81, 74, 68, 78, 76, 77, 85, 95, 93};
        int[] scores2 = Arrays.copyOf(scores1, scores1.length);
        for(int score : scores2) {
            System.out.printf("%3d", score);
        }
        System.out.println();
        scores2[0] = 99;
        // 不影響score1參考的陣列物件
        for(int score : scores1) {
            System.out.printf("%3d", score);
        }
    }
}

執行結果如下所示:

88 81 74 68 78 76 77 85 95 93
88 81 74 68 78 76 77 85 95 93

在Java中,陣列一旦建立,長度就固定了。如果你事先建立的陣列長度不夠怎麼辦?那就只好建立新陣列,將原陣列內容複製至新陣列。例如:

int[] scores1 = {88, 81, 74, 68, 78, 76, 77, 85, 95, 93};
int[] scores2 = Arrays.copyOf(scores1, scores1.length * 2);
for(int score : scores2) {
    System.out.printf("%3d", score);
}

Arrays.copyOf()的第二個參數,實際上就是指定建立的新陣列長度。上面這個程式片段建立的新陣列是20,執行結果顯示原scores1複製過去的88到93的元素,而後顯示10個預設值0。

以上都是示範基本型態陣列,對於類別型態宣告的陣列則要注意參考的行為。直接來看個範例:

package cc.openhome;

class Clothes {
    String color;
    char size;
    Clothes(String color, char size) {
        this.color = color;
        this.size = size;
    }
}

public class ShallowCopy {
    public static void main(String[] args) {
        Clothes[] c1 = {new Clothes("red", 'L'), new Clothes("blue", 'M')};
        Clothes[] c2 = new Clothes[c1.length];
        for(int i = 0; i < c1.length; i++) {
            c2[i] = c1[i];
        }
        c1[0].color = "yellow";
        System.out.println(c2[0].color);
    }
} 

這個程式的執行結果會是yellow,這是怎麼回事?原因在於迴圈執行完畢後,用圖來表示的話就是:

淺層複製

實際上迴圈中僅將c1每個索引處所參考的物件,也給c2每個索引來參考,並沒有實際複製出Clothes物件,術語上來說,這叫作複製參考,或稱這個行為是淺層複製(Shallow copy)。無論是System.arraycopy()Arrays.copyOf(),用在類別型態宣告的陣列時,都是執行淺層複製。如果真的要連同物件一同複製,你得自行實作,因為基本上只有自己才知道,每個物件複製時,有哪些屬性必須複製。例如:

package cc.openhome;

class Clothes2 {
    String color;
    char size;
    Clothes2(String color, char size) {
        this.color = color;
        this.size = size;
    }
}

public class DeepCopy {
    public static void main(String[] args) {
        Clothes2[] c1 = {new Clothes2("red", 'L'), new Clothes2("blue", 'M')};
        Clothes2[] c2 = new Clothes2[c1.length];
        for(int i = 0; i < c1.length; i++) {
            Clothes2 c = new Clothes2(c1[i].color, c1[i].size);
            c2[i] = c;
        }
        c1[0].color = "yellow";
        System.out.println(c2[0].color);
    }
}

這個範例執行所謂深層複製(Deep copy)行為,也就是實際上c1每個索引參考的物件會被複製,分別指定給c2每個索引,結果就是顯示red。在迴圈執行完畢後,用圖來表示參考與物件之間的關係會是:

深層複製