第 2 章 Java 言語の基本的な文法 I 5
2.18 メソッド I
2.18.7 System.arraycopy
配列間でデータをコピーするプログラムは、配列を理解する上で省けない基本操作です。
まずは、以前作った以下のソースコード??のプログラムを見て下さい。
ソースコード2.82 ArrayCopy.java (配列のコピー) package section0214;
import java.util.Arrays;
public class ArrayCopy {
public static void main(String[] args) { int[] x = { 1, 3, 0, -2, 4 };
int[] y = new int[x.length];
for(int i=0; i<x.length; i++) {
y[i] = x[i];
}
// ちゃんとコピーされたか y を出力してみる
System.out.println( "y = " + Arrays.toString( y ) );
} }
ベタに要素一つずつを対応する位置に格納してやることが基本となります。とはいえ、ちょっとダサい処理ですね。
そこで、これをメソッドを使ってスッキリと実現する方法を紹介しましょう。
ソースコード2.83 ArrayCopyExample1.java (1次元配列のコピー) package section0218;
import java.util.Arrays;
public class ArrayCopyExample1 {
public static void main(String[] args) { int[] x = { 1, 3, 0, -2, 4 };
int[] y = new int[x.length];
System.arraycopy(x, 0, y, 0, x.length);
// ちゃんとコピーされたか y を出力してみる
System.out.println( "y = " + Arrays.toString( y ) );
} }
Systemクラスのクラスメソッドarraycopyは5つの引数を持つメソッドで、配列間でデータのコピーを行います。
arraycopy(a1,s1,a2,s2,len)は、「配列 a1の [s1]番要素を、配列 a2の[s2] 番要素へコピーすることを開始と して、順に len個分のデータをコピーする」という処理を行います。つまり、上のプログラムでは、同じ大きさの2つ の配列間で全ての要素をコピーしていることになりますね。lenの値が配列の長さより短い場合、余った部分はデフォ ルトの0 が代入されます。なお、lenが負だったり配列長より長かったりしてもコンパイルエラーにはならず、実行 時にArrayIndexOutOfBound例外を投げるので、一般には事前のチェックが必要となります。
では、2次元配列のコピーはどのように行いましょうか。これも前に問題2.14.17において作ったプログラムを書き 換えてみましょう。
ソースコード2.84 ArrayCopy3.java (多次元配列のコピー) package section0214;
import java.util.Arrays;
public class ArrayCopy3 {
public static void main(String[] args) { int[][] A = { {1, 3, 0}, {-2, 4} };
int[][] B = new int[2][];
for(int i=0; i<A.length; i++) { B[i] = new int[A[i].length];
for(int j=0; j<A[i].length; j++) { B[i][j] = A[i][j];
} }
System.out.println( "B = " + Arrays.deepToString( B ) );
} }
まず、駄目な方法から。
ソースコード2.85 ArrayCopyExample2.java (多次元配列のコピー:失敗作)
package section0218;
import java.util.Arrays;
public class ArrayCopyExample2 {
public static void main(String[] args) { int[][] A = { {1, 3, 0}, {-2, 4} };
int[][] B = new int[2][];
System.arraycopy(A, 0, B, 0, A.length);
A[0][0] = 0; // うまく行っているかのチェック
System.out.println( "B = " + Arrays.deepToString( B ) );
} }
一見出来ているように見えますが、 B[0][0]の値が 0に変化しています。理由はもうOKですね。こうした、表面上 はコピーしているようで、実際は同じデータを参照しているようなコピーを「浅いコピー(Shallow copy)」と言いま す。一方、新たな配列として中身までちゃんと別に作成する次のようなコピーを「深いコピー(Deep copy)」と言い ます。当然、コピーは深いコピーでないといけません。
Arrays.toString() に対するArrays.deepToString()のように多次元配列に拡張されたメソッドがあれば良いの ですが、残念ながら無いので次のようにします。
ソースコード2.86 ArrayCopyExample3.java (多次元配列のコピー) package section0218;
import java.util.Arrays;
public class ArrayCopyExample3 {
public static void main(String[] args) { int[][] A = { {1, 3, 0}, {-2, 4} };
int[][] B = new int[2][];
for(int i=0; i<A.length; i++) { B[i] = new int[A[i].length];
for(int j=0; j<A[i].length; j++) {
System.arraycopy(A[i], 0, B[i], 0, A[i].length);
} }
System.out.println( "B = " + Arrays.deepToString( B ) );
} }
「ところで、clone()というメソッドが配列にはあるようですが、これは使えないのでしょうか?」ほお、勉強して いますね。では、cloneメソッドを使ったコピーのプログラムを作ってみましょう。
ソースコード2.87 ArrayCloneExample.java (配列のクローン) package section0218;
import java.util.Arrays;
public class ArrayCloneExample {
public static void main(String[] args) { // 1次元配列の場合
int[] x = { 1, 3, 0, -2, 4 };
int[] y = x.clone();
x[0] = 0; // Deep copy になっているかのチェック // ちゃんとコピーされたか y を出力してみる
System.out.println( "y = " + Arrays.toString( y ) );
// 多次元配列の場合
int[][] A = { {1, 3, 0}, {-2, 4} };
int[][] B = new int[A.length][];
for(int i=0; i<A.length; i++) { B[i] = A[i].clone();
}
A[0][0] = 0; // Deep copy になっているかのチェック // ちゃんとコピーされたか B を出力してみる
System.out.println( Arrays.deepToString( B ));
} }
clone メソッドの場合も、多次元配列には対応していないので、2次元配列はarraycopy と同様な書き方になりま
す。ただ、コピー先の配列の大きさを宣言せずに済んでいる分プログラムが簡潔になっていますね。
さて、コピーには2通りあることがわかりましたが、arraycopyメソッドはあくまで配列のコピーのみです。一方、
cloneメソッドは後期に学ぶオブジェクト一般でのコピーに使えます。ただし、浅いコピーになっていないか?の判断
が大事です。後期にまた触れることがあるかな?