記事内にはプロモーションが含まれています

【Java】配列をコピーする4つの方法!cloneやSystem.arraycopyの使い分けとディープコピーの罠

【Java】配列をコピーする4つの方法!cloneやSystem.arraycopyの使い分けとディープコピーの罠 Java

Javaで開発を行っていると、既存の配列を複製して、元のデータには影響を与えずに加工したいという場面が頻繁にあります。

しかし、Javaの配列は「オブジェクト」として扱われるため、単に = 演算子で代入しただけではコピーを作ることができません。

「コピーしたはずの配列を変更したら、元の配列まで書き換わってしまった!」というバグは、Java初心者が必ず一度は通る道です。

この記事では、Javaにおける配列の正しいコピー方法について、手軽な clone()Arrays.copyOf()、高速な System.arraycopy() などの使い分けを解説します。

さらに、2次元配列やオブジェクト配列で発生する「シャローコピー(浅いコピー)」の問題と、それを回避する「ディープコピー(深いコピー)」の実装方法まで、現在の開発現場で役立つ知識を徹底解説します。

【本記事の信頼性】
プロフィール
執筆者:マヒロ
  • 執筆者は元エンジニア
  • SES⇒大手の社内SE⇒独立
  • 現在はこじんまりとしたプログラミングスクールを運営
  • モットーは「利他の精神」

配列コピーの基本と「参照渡し」の落とし穴

まず、なぜJavaの配列コピーで多くの人がつまずくのか、その原因を確認しておきましょう。

「=」で代入してもコピーにはならない

変数をコピーする感覚で、配列変数を別の変数に代入しても、複製は作られません。
コピーされるのは「データの実体」ではなく、「データがどこにあるかを示す住所(参照)」だけだからです。

public class ReferenceCopyExample {
    public static void main(String[] args) {
        int[] original = {1, 2, 3};
        int[] copy = original; // 参照のみをコピー(同じ実体を見る)

        copy[0] = 99; // コピー先を変更

        // 元の配列も変わってしまう
        System.out.println("original[0]: " + original[0]); 
        System.out.println("copy[0]: " + copy[0]); 
    }
}

実行結果

original[0]: 99
copy[0]: 99

int[] copy = original; とした時点で、originalcopy はメモリ上の同じ配列データを指し示しています。

そのため、copy を変更すると、当然ながら original も変更されたように見えます。

独立した別の配列として扱いたい場合は、新しいメモリ領域を確保して値を移し替える「複製」処理が必要です。

配列を正しく複製する3つの主要な方法

Javaで配列を複製(コピー)するには、主に以下の3つの方法が使われます。
用途や好みに応じて使い分けるのがベストです。

1. clone() メソッド(最も手軽)

配列オブジェクトが標準で持っている clone() メソッドを使う方法です。
記述量が少なく、型変換(キャスト)も不要なため、最も簡単でよく使われます。

int[] original = {1, 2, 3};
int[] copy = original.clone();

2. Arrays.copyOf()(可読性が高い)

java.util.Arrays クラスの便利メソッドを使う方法です。

「配列をコピーする」という意図が読み取りやすく、さらに「新しい配列のサイズ(長さ)」を指定できるのが特徴です。

import java.util.Arrays;

int[] original = {1, 2, 3};
// 第2引数でコピーする長さを指定(元のサイズと同じなら完全コピー)
int[] copy = Arrays.copyOf(original, original.length);

3. System.arraycopy()(高速・詳細制御)

System クラスのメソッドを使う方法です。 他の方法に比べて記述が少し複雑ですが、処理速度が最も高速です。

また、「コピー元の開始位置」や「コピー先の開始位置」を細かく指定できるため、配列の一部だけをコピーしたい場合などに威力を発揮します。

int[] original = {1, 2, 3};
int[] copy = new int[original.length]; // 先に箱を作る必要がある

// (コピー元, 元の開始位置, コピー先, 先の開始位置, コピーする個数)
System.arraycopy(original, 0, copy, 0, original.length);

シャローコピーとディープコピーの違いに注意

intdouble などのプリミティブ型(基本データ型)の配列であれば、上記の方法で問題なく複製できます。

しかし、「2次元配列」や「オブジェクトの配列」の場合、上記の方法では不完全なコピー(シャローコピー)になってしまうため注意が必要です。

シャローコピー(浅いコピー)とは

配列という「入れ物」だけを新しく作り、中身(要素)は元の配列と同じオブジェクトを参照している状態です。

clone()Arrays.copyOf() は、基本的にこのシャローコピーを行います。

// 2次元配列のシャローコピー問題
public class ShallowCopyExample {
    public static void main(String[] args) {
        int[][] original = {{1, 2}, {3, 4}};
        int[][] copy = original.clone(); // 2次元配列をclone

        // コピー先の中身を変更
        copy[0][0] = 99;

        // 元の配列も変わってしまう!
        System.out.println("original[0][0]: " + original[0][0]);
    }
}

実行結果

original[0][0]: 99

original.clone() で新しい2次元配列を作りましたが、その中に入っている「1次元配列(行)」への参照はコピーされていません。

つまり、copy[0]original[0] は同じ配列の実体を共有しているため、中身を書き換えると両方に影響が出ます。

ディープコピー(深いコピー)を実現する方法

元の配列と完全に独立したコピーを作るには、配列の階層をたどって、一つひとつの要素を新しく作り直す「ディープコピー」を実装する必要があります。

2次元配列のディープコピー例

public class DeepCopyExample {
    public static void main(String[] args) {
        int[][] original = {{1, 2}, {3, 4}};
        
        // 元の配列と同じサイズで新しい配列を作成
        int[][] copy = new int[original.length][];

        // ループを使って、行(内側の配列)ごとにコピーする
        for (int i = 0; i < original.length; i++) {
            copy[i] = original[i].clone();
        }

        // コピー先を変更
        copy[0][0] = 99;

        // 元の配列は影響を受けない
        System.out.println("original[0][0]: " + original[0][0]); 
        System.out.println("copy[0][0]: " + copy[0][0]); 
    }
}

実行結果

original[0][0]: 1
copy[0][0]: 99

for 文を使って、外側の配列だけでなく、内側の配列ひとつひとつに対して clone() を実行しています。

これにより、すべての階層で新しい実体が生成され、完全に独立した2次元配列が完成しました。

3次元以上の配列や、自作クラスの配列の場合も同様に、中身を一つずつ複製する処理が必要です。

【応用】配列の一部だけを切り出してコピーする

「配列の3番目から5番目までを抜き出したい」といった場合は、Arrays.copyOfRange メソッドが便利です。

import java.util.Arrays;

public class RangeCopyExample {
    public static void main(String[] args) {
        int[] original = {10, 20, 30, 40, 50};

        // インデックス1から、インデックス4の手前(3)までをコピー
        int[] subArray = Arrays.copyOfRange(original, 1, 4);

        // 配列の中身を表示
        System.out.println(Arrays.toString(subArray));
    }
}

実行結果

[20, 30, 40]

Arrays.copyOfRange(元配列, 開始インデックス, 終了インデックス) を使用します。

重要なのは、第3引数の「終了インデックス」は範囲に含まれない(未満)という点です。
上記の例では 1, 4 と指定しているため、インデックス 1, 2, 3 の要素がコピーされます。

Javaのスキルを活かして年収を上げる方法

以上、Javaで配列をコピーする方法について解説してきました。

なお、Javaのスキルがある場合には、「転職して年収をアップさせる」「副業で稼ぐ」といった方法を検討するのがおすすめです。

Javaエンジニアの需要は非常に高いため、転職によって数十万円の年収アップはザラで、100万円以上年収が上がることも珍しくありません。

なお、転職によって年収を上げたい場合は、エンジニア専門の転職エージェントサービスを利用するのが最適です。

今すぐ転職する気がなくとも、とりあえず転職エージェントに無料登録しておくだけで、スカウトが届いたり、思わぬ好待遇の求人情報が送られてきたりするというメリットがあります。

併せて、副業案件を獲得できるエージェントにも登録しておくと、空いている時間を活かして稼げるようなJavaの案件を探しやすくなります。

転職エージェントも副業エージェントも、登録・利用は完全無料なので、どんな求人や副業案件があるのか気になる方は、気軽に利用してみるとよいでしょう。