- JAVA
Java配列コピー4つの方法|copyOf・arraycopy・clone
Javaの配列コピーとは、元の配列と同じ中身を持つ別の配列を作ることで、主な方法は clone()・Arrays.copyOf・Arrays.copyOfRange・System.arraycopy の4つです。 全体をそのまま写すなら clone()、長さを変えながら写すなら Arrays.copyOf、一部分だけ取り出すなら Arrays.copyOfRange、すでにある配列の好きな位置へ写すなら System.arraycopy が合います。
この記事では、4つの方法を引数・長さ変更の可否・用途で比べる早見表を最初に置き、すべてのコードを OpenJDK 21.0.10 で動かした出力つきで説明します。2次元配列やオブジェクト配列で「写しを書き換えたら元も変わった」という動きの再現、深いコピーの書き方、2つの配列を結合するコード(arraycopy版とStream版)まで、コピーして使える形でまとめました。
配列コピー4つの方法の早見表
| 方法 | 書き方 | 引数 | 長さを変えられるか | コピー先の配列 | 向いている用途 |
|---|---|---|---|---|---|
clone() |
src.clone() |
なし | 変えられない(元と同じ) | 自動で作られる | 全体をそのまま写す |
Arrays.copyOf |
Arrays.copyOf(src, 長さ) |
元の配列、新しい長さ | 変えられる(短く・長く) | 自動で作られる | 先頭から写す、配列を広げる |
Arrays.copyOfRange |
Arrays.copyOfRange(src, 開始, 終了) |
元の配列、開始位置、終了位置(その手前まで) | 変えられる(範囲で決まる) | 自動で作られる | 一部分を取り出す |
System.arraycopy |
System.arraycopy(src, 元の位置, dest, 先の位置, 個数) |
元の配列、元の開始位置、コピー先、先の開始位置、個数 | コピー先の長さによる | 自分で用意する | 既存の配列へ写す、結合、ずらす |
やりたいことから選ぶときは、次の表を使ってください。
| やりたいこと | コード |
|---|---|
| 全体をコピーする | int[] copy = src.clone(); |
| 先頭3件を取り出す | Arrays.copyOf(src, 3) |
| 末尾に空きを作って広げる | Arrays.copyOf(src, src.length + 1) |
| インデックス1〜3を取り出す | Arrays.copyOfRange(src, 1, 4) |
| 2つの配列を結合する | Arrays.copyOf で広げてから System.arraycopy |
| 2次元配列を行ごと独立させてコピーする | Arrays.stream(grid).map(int[]::clone).toArray(int[][]::new) |
| オブジェクト配列を要素ごと独立させてコピーする | 要素ごとに new して入れ直す |
4つの方法はどれも「浅いコピー」です。 int や double などの基本型の配列ならそのままで独立した写しになりますが、2次元配列やオブジェクト配列では、後半で説明する深いコピーが必要になる場合があります。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java配列コピー4つの方法|copyOf・arraycopy・clone」
代入はコピーではないことを確認する
最初に押さえておきたいのは、int[] same = src; という代入は配列のコピーではないという点です。配列の変数には配列そのものではなく「配列の場所(参照)」が入っているので、代入すると同じ配列を指す変数が2つになるだけです。
import java.util.Arrays;
public class Assign {
public static void main(String[] args) {
int[] src = {10, 20, 30};
int[] same = src; // 代入:同じ配列を指す変数が2つになるだけ
int[] copy = src.clone(); // コピー:中身が同じ別の配列ができる
same[0] = -1;
copy[1] = 999;
System.out.println("src : " + Arrays.toString(src));
System.out.println("same : " + Arrays.toString(same));
System.out.println("copy : " + Arrays.toString(copy));
System.out.println("same == src ? " + (same == src));
System.out.println("copy == src ? " + (copy == src));
}
}
実行結果(OpenJDK 21.0.10、java Assign.java):
src : [-1, 20, 30]
same : [-1, 20, 30]
copy : [10, 999, 30]
same == src ? true
copy == src ? false
same[0] を書き換えると src[0] も -1 になりました。一方、clone() で作った copy を書き換えても src は変わっていません。== で比べると、代入は true(同じ配列)、コピーは false(別の配列) になります。
flowchart LR
src[変数 src] --> A["配列 10, 20, 30"]
same[変数 same] --> A
copy[変数 copy] --> B["別の配列 10, 20, 30"]
メソッドに配列を渡すときも同じ仕組みです。受け取った側で要素を書き換えると、呼び出し元の配列も変わります。元の配列を残したいときは、渡す前か受け取った直後にコピーを作ります。
4つの方法を実行結果で比べる
同じ配列を4つの方法でコピーし、結果を並べます。
import java.util.Arrays;
public class CopyFour {
public static void main(String[] args) {
int[] src = {10, 20, 30, 40, 50};
// 1. clone:配列全体を同じ長さでコピー
int[] a = src.clone();
// 2. Arrays.copyOf:先頭から、指定した長さでコピー
int[] b1 = Arrays.copyOf(src, src.length);
int[] b2 = Arrays.copyOf(src, 3); // 短くする
int[] b3 = Arrays.copyOf(src, 7); // 長くする(余りは 0)
// 3. Arrays.copyOfRange:開始位置〜終了位置の手前までをコピー
int[] c1 = Arrays.copyOfRange(src, 1, 4);
int[] c2 = Arrays.copyOfRange(src, 3, 8); // 終了位置が長さを超えると 0 で埋まる
// 4. System.arraycopy:用意済みの配列の、指定した位置へコピー
int[] d1 = new int[src.length];
System.arraycopy(src, 0, d1, 0, src.length);
int[] d2 = new int[8];
System.arraycopy(src, 1, d2, 2, 3); // src[1]から3個を、d2[2]以降へ
System.out.println("clone() : " + Arrays.toString(a));
System.out.println("copyOf(src, 5) : " + Arrays.toString(b1));
System.out.println("copyOf(src, 3) : " + Arrays.toString(b2));
System.out.println("copyOf(src, 7) : " + Arrays.toString(b3));
System.out.println("copyOfRange(src,1,4) : " + Arrays.toString(c1));
System.out.println("copyOfRange(src,3,8) : " + Arrays.toString(c2));
System.out.println("arraycopy 全体 : " + Arrays.toString(d1));
System.out.println("arraycopy 一部 : " + Arrays.toString(d2));
}
}
実行結果:
clone() : [10, 20, 30, 40, 50]
copyOf(src, 5) : [10, 20, 30, 40, 50]
copyOf(src, 3) : [10, 20, 30]
copyOf(src, 7) : [10, 20, 30, 40, 50, 0, 0]
copyOfRange(src,1,4) : [20, 30, 40]
copyOfRange(src,3,8) : [40, 50, 0, 0, 0]
arraycopy 全体 : [10, 20, 30, 40, 50]
arraycopy 一部 : [0, 0, 20, 30, 40, 0, 0, 0]
cloneで全体をコピーする
clone() は引数がなく、元と同じ長さ・同じ型の配列を1行で作れます。 配列の clone() は戻り値の型が元の配列と同じなので、キャストも例外処理も必要ありません。全体をそのまま写したいときに、いちばん短く書ける方法です。
Arrays.copyOfで長さを変えてコピーする
Arrays.copyOf(src, 長さ) は、先頭から指定した長さぶんをコピーします。元より短い長さを指定すると後ろが切り捨てられ、長い長さを指定すると余った部分が初期値で埋まります。使うには import java.util.Arrays; が必要です。
余った部分に入る値は、配列の型によって決まります。
import java.util.Arrays;
public class Padding {
public static void main(String[] args) {
int[] nums = {1, 2};
double[] rates = {1.5};
boolean[] flags = {true};
char[] chars = {'A'};
String[] names = {"佐藤", "鈴木"};
System.out.println("int[] : " + Arrays.toString(Arrays.copyOf(nums, 4)));
System.out.println("double[] : " + Arrays.toString(Arrays.copyOf(rates, 3)));
System.out.println("boolean[] : " + Arrays.toString(Arrays.copyOf(flags, 3)));
System.out.println("char[] : " + (int) Arrays.copyOf(chars, 2)[1] + "(文字コード)");
System.out.println("String[] : " + Arrays.toString(Arrays.copyOf(names, 4)));
}
}
実行結果:
int[] : [1, 2, 0, 0]
double[] : [1.5, 0.0, 0.0]
boolean[] : [true, false, false]
char[] : 0(文字コード)
String[] : [佐藤, 鈴木, null, null]
| 配列の型 | 余った部分に入る値 |
|---|---|
int[]、long[]、short[]、byte[] |
0 |
double[]、float[] |
0.0 |
boolean[] |
false |
char[] |
文字コード0の文字 |
String[] などの参照型 |
null |
参照型の配列を広げると、余った部分は null になります。 広げた直後に要素のメソッドを呼ぶと NullPointerException になるので、値を入れてから使います。
Arrays.copyOfRangeで一部分を取り出す
Arrays.copyOfRange(src, 開始, 終了) は、開始位置から「終了位置の手前」までをコピーします。copyOfRange(src, 1, 4) なら、インデックス1・2・3の3件です。取り出される個数は「終了 − 開始」になります。
終了位置は元の配列の長さを超えてもかまいません。実行結果の copyOfRange(src,3,8) のように、超えた部分は0で埋まります。Java 21 の公式APIドキュメント(Arrays クラス)を開いて確認したところ、終了位置は配列の外にあってもよいこと、開始位置が終了位置より大きいと IllegalArgumentException になることが書かれています。
System.arraycopyで既存の配列へコピーする
System.arraycopy は引数が5つあります。ほかの3つと違い、コピー先の配列を自分で用意し、戻り値はありません。
System.arraycopy(src, 1, dest, 2, 3);
// ① ② ③ ④ ⑤
| 番号 | 引数 | 意味 | 上の例 |
|---|---|---|---|
| ① | src | コピー元の配列 | src |
| ② | srcPos | コピー元の開始位置 | 1(src[1] から) |
| ③ | dest | コピー先の配列 | dest |
| ④ | destPos | コピー先の開始位置 | 2(dest[2] から) |
| ⑤ | length | コピーする個数 | 3個 |
実行結果の「arraycopy 一部」では、src[1]〜src[3] の3個(20, 30, 40)が、長さ8の配列のインデックス2〜4に入っています。コピー先の途中の位置を指定できるのは System.arraycopy だけなので、配列の結合や、要素をずらす処理に向いています。import は不要です。
どの方法を選ぶかの判断表
| 条件 | 選ぶ方法 |
|---|---|
| 全体を同じ長さで写したい | clone() |
| 先頭からN件ほしい、または長さを広げたい | Arrays.copyOf |
| 途中の範囲だけほしい | Arrays.copyOfRange |
| すでにある配列の途中へ書き込みたい | System.arraycopy |
| 複数の配列を1つにまとめたい | Arrays.copyOf と System.arraycopy の組み合わせ |
| 条件に合う要素だけを写したい | for文、または Stream の filter |
for文で1つずつ代入する方法でもコピーはできます。要素ごとに値を変換したり、条件で選んだりするときは for文が素直です。単純に写すだけなら、4つの方法のほうが短く、範囲の書き間違いも減らせます。処理時間の比較は、今回は安定した計測ができなかったため載せていません(未実行扱い)。
コピーで出会う例外とメッセージ
引数を取り違えたときに、どのような例外とメッセージが出るかを実行して確認しました。
import java.util.Arrays;
public class Errs {
static void test(String label, Runnable r) {
try {
r.run();
System.out.println(label + " -> 例外なし");
} catch (RuntimeException e) {
System.out.println(label + "\n -> " + e.getClass().getName() + ": " + e.getMessage());
}
}
public static void main(String[] args) {
int[] src = {10, 20, 30, 40, 50};
test("arraycopy:コピー先が短い", () -> System.arraycopy(src, 0, new int[3], 0, 5));
test("arraycopy:型が違う", () -> System.arraycopy(src, 0, new long[5], 0, 5));
test("arraycopy:コピー先が null", () -> System.arraycopy(src, 0, null, 0, 5));
test("copyOf:長さが負の数", () -> Arrays.copyOf(src, -1));
test("copyOfRange:開始 > 終了", () -> Arrays.copyOfRange(src, 4, 2));
test("copyOfRange:開始が長さを超える", () -> Arrays.copyOfRange(src, 6, 8));
}
}
実行結果:
arraycopy:コピー先が短い
-> java.lang.ArrayIndexOutOfBoundsException: arraycopy: last destination index 5 out of bounds for int[3]
arraycopy:型が違う
-> java.lang.ArrayStoreException: arraycopy: type mismatch: can not copy int[] into long[]
arraycopy:コピー先が null
-> java.lang.NullPointerException: null
copyOf:長さが負の数
-> java.lang.NegativeArraySizeException: -1
copyOfRange:開始 > 終了
-> java.lang.IllegalArgumentException: 4 > 2
copyOfRange:開始が長さを超える
-> java.lang.ArrayIndexOutOfBoundsException: arraycopy: length -1 is negative
| 例外 | 起きる状況 | 直し方 |
|---|---|---|
| ArrayIndexOutOfBoundsException | arraycopy でコピー先が足りない |
コピー先を「先の開始位置 + 個数」以上の長さで用意する |
| ArrayStoreException | arraycopy で元と先の型が違う |
同じ型の配列を用意する |
| NullPointerException | 元またはコピー先が null | 配列を new で作ってから渡す |
| NegativeArraySizeException | copyOf に負の長さを渡した |
長さの計算式を見直す |
| IllegalArgumentException | copyOfRange で開始が終了より大きい |
開始と終了の順番を見直す |
last destination index 5 out of bounds for int[3] は「コピー先の最後の位置が5になるが、配列の長さは3」という意味です。メッセージに数字が入っているので、どの引数を直せばよいかが読み取れます。
浅いコピーでコピー元まで書き換わる例
ここからが、配列コピーでいちばんつまずきやすい部分です。4つの方法は、配列の「1段目」だけを新しく作ります。 要素が配列やオブジェクトの場合、写しの要素は元と同じものを指したままになります。これを浅いコピー(シャローコピー)と呼びます。
import java.util.Arrays;
public class ShallowDemo {
static class Item {
String name;
int stock;
Item(String name, int stock) { this.name = name; this.stock = stock; }
@Override public String toString() { return name + ":" + stock; }
}
public static void main(String[] args) {
// (1) 2次元配列を clone でコピーして、写しを書き換える
int[][] grid = { {1, 2, 3}, {4, 5, 6} };
int[][] gridCopy = grid.clone();
gridCopy[0][0] = 99;
System.out.println("(1) 元 : " + Arrays.deepToString(grid));
System.out.println(" 写し: " + Arrays.deepToString(gridCopy));
System.out.println(" gridCopy == grid ? " + (gridCopy == grid));
System.out.println(" gridCopy[0] == grid[0] ? " + (gridCopy[0] == grid[0]));
// (2) オブジェクト配列を Arrays.copyOf でコピーして、写しの要素の中身を書き換える
Item[] items = { new Item("ペン", 10), new Item("ノート", 5) };
Item[] itemsCopy = Arrays.copyOf(items, items.length);
itemsCopy[0].stock = 0;
System.out.println("(2) 元 : " + Arrays.toString(items));
System.out.println(" 写し: " + Arrays.toString(itemsCopy));
// (3) 写しの要素を「別のオブジェクトに差し替える」場合は、元に影響しない
itemsCopy[1] = new Item("消しゴム", 3);
System.out.println("(3) 元 : " + Arrays.toString(items));
System.out.println(" 写し: " + Arrays.toString(itemsCopy));
}
}
実行結果:
(1) 元 : [[99, 2, 3], [4, 5, 6]]
写し: [[99, 2, 3], [4, 5, 6]]
gridCopy == grid ? false
gridCopy[0] == grid[0] ? true
(2) 元 : [ペン:0, ノート:5]
写し: [ペン:0, ノート:5]
(3) 元 : [ペン:0, ノート:5]
写し: [ペン:0, 消しゴム:3]
(1) では、写しの gridCopy[0][0] を99に変えただけなのに、元の grid[0][0] も99になりました。 gridCopy == grid は false なので外側の配列は別物ですが、gridCopy[0] == grid[0] は true で、行の配列は同じものを指しています。
(2) も同じ仕組みです。写しの itemsCopy[0].stock を0にすると、元の items[0] の在庫も0になりました。配列の中に入っているのは Item そのものではなく Item の場所なので、コピーしても同じ Item を指します。
(3) は、写しの要素を別のオブジェクトに差し替えた場合です。このときは元の配列に影響しません。「要素の中身を書き換える」と元に伝わり、「要素を差し替える」と伝わらない、と整理すると覚えやすくなります。
flowchart LR
G[grid] --> R0["行0: 1, 2, 3"]
G --> R1["行1: 4, 5, 6"]
C[gridCopy] --> R0
C --> R1
4つの方法はどれも浅いコピーになる
「clone() 以外なら深くコピーされるのでは」と思うかもしれませんが、4つとも同じ動きです。
import java.util.Arrays;
public class AllShallow {
public static void main(String[] args) {
int[][] grid = { {1, 2}, {3, 4} };
int[][] a = grid.clone();
int[][] b = Arrays.copyOf(grid, grid.length);
int[][] c = Arrays.copyOfRange(grid, 0, grid.length);
int[][] d = new int[grid.length][];
System.arraycopy(grid, 0, d, 0, grid.length);
a[0][0] = 10; // clone の写しを書き換え
b[0][1] = 20; // copyOf の写しを書き換え
c[1][0] = 30; // copyOfRange の写しを書き換え
d[1][1] = 40; // arraycopy の写しを書き換え
System.out.println("元の配列: " + Arrays.deepToString(grid));
}
}
実行結果:
元の配列: [[10, 20], [30, 40]]
4つの写しをそれぞれ1か所ずつ書き換えたところ、元の配列の4か所すべてが変わりました。 方法を変えても、浅いコピーであることは変わりません。
| 配列の種類 | 浅いコピーで独立するか | 理由 |
|---|---|---|
int[] などの基本型 |
独立する | 要素に値そのものが入っている |
String[] |
独立して使える | String は中身を書き換えられない |
| record や不変クラスの配列 | 独立して使える | フィールドを書き換えられない |
int[][] などの多次元配列 |
行を共有する | 要素が配列の参照 |
| フィールドを書き換えられるクラスの配列 | オブジェクトを共有する | 要素がオブジェクトの参照 |
深いコピーの書き方
要素まで含めて独立した写しを作ることを、深いコピー(ディープコピー)と呼びます。Javaには配列を自動で深くコピーする標準メソッドがないので、要素ごとにコピーを作って入れ直します。
import java.util.Arrays;
public class DeepCopy {
static class Item {
String name;
int stock;
Item(String name, int stock) { this.name = name; this.stock = stock; }
Item(Item other) { this(other.name, other.stock); } // コピーコンストラクタ
@Override public String toString() { return name + ":" + stock; }
}
public static void main(String[] args) {
// (1) 2次元配列:行ごとに clone する
int[][] grid = { {1, 2, 3}, {4, 5, 6} };
int[][] deep = new int[grid.length][];
for (int i = 0; i < grid.length; i++) {
deep[i] = grid[i].clone();
}
deep[0][0] = 99;
System.out.println("(1) 元 : " + Arrays.deepToString(grid));
System.out.println(" 写し: " + Arrays.deepToString(deep));
// (2) 2次元配列:Stream で1行にまとめる
int[][] deep2 = Arrays.stream(grid).map(int[]::clone).toArray(int[][]::new);
deep2[1][2] = -1;
System.out.println("(2) 元 : " + Arrays.deepToString(grid));
System.out.println(" 写し: " + Arrays.deepToString(deep2));
// (3) オブジェクト配列:要素ごとに新しいオブジェクトを作る
Item[] items = { new Item("ペン", 10), new Item("ノート", 5) };
Item[] deepItems = new Item[items.length];
for (int i = 0; i < items.length; i++) {
deepItems[i] = new Item(items[i]);
}
deepItems[0].stock = 0;
System.out.println("(3) 元 : " + Arrays.toString(items));
System.out.println(" 写し: " + Arrays.toString(deepItems));
// (4) オブジェクト配列:Stream で1行にまとめる
Item[] deepItems2 = Arrays.stream(items).map(Item::new).toArray(Item[]::new);
deepItems2[1].stock = 77;
System.out.println("(4) 元 : " + Arrays.toString(items));
System.out.println(" 写し: " + Arrays.toString(deepItems2));
}
}
実行結果:
(1) 元 : [[1, 2, 3], [4, 5, 6]]
写し: [[99, 2, 3], [4, 5, 6]]
(2) 元 : [[1, 2, 3], [4, 5, 6]]
写し: [[1, 2, 3], [4, 5, -1]]
(3) 元 : [ペン:10, ノート:5]
写し: [ペン:0, ノート:5]
(4) 元 : [ペン:10, ノート:5]
写し: [ペン:10, ノート:77]
4つとも、写しを書き換えても元の配列はそのままです。
| 対象 | ループで書く | Stream で書く |
|---|---|---|
| 2次元配列 | 行ごとに grid[i].clone() |
Arrays.stream(grid).map(int[]::clone).toArray(int[][]::new) |
| オブジェクト配列 | 要素ごとに new Item(items[i]) |
Arrays.stream(items).map(Item::new).toArray(Item[]::new) |
オブジェクト配列では、クラスに「同じ型を受け取って中身を写すコンストラクタ」(コピーコンストラクタ)を用意しておくと、深いコピーを短く書けます。上の例では Item(Item other) がそれに当たります。フィールドの中にさらに配列やオブジェクトがある場合は、コピーコンストラクタの中でそれらもコピーします。
浅いコピーで十分な場合
深いコピーは、いつも必要なわけではありません。要素の中身を書き換えられない型なら、浅いコピーのままで困りません。
import java.util.Arrays;
public class Immutable {
record Point(int x, int y) {}
public static void main(String[] args) {
// String は中身を書き換えられないので、浅いコピーで十分
String[] names = {"佐藤", "鈴木"};
String[] namesCopy = names.clone();
namesCopy[0] = namesCopy[0] + "さん";
System.out.println("元 : " + Arrays.toString(names));
System.out.println("写し: " + Arrays.toString(namesCopy));
// record もフィールドを書き換えられないので、浅いコピーで十分
Point[] points = { new Point(1, 2) };
Point[] pointsCopy = points.clone();
pointsCopy[0] = new Point(9, 9);
System.out.println("元 : " + Arrays.toString(points));
System.out.println("写し: " + Arrays.toString(pointsCopy));
}
}
実行結果:
元 : [佐藤, 鈴木]
写し: [佐藤さん, 鈴木]
元 : [Point[x=1, y=2]]
写し: [Point[x=9, y=9]]
String は連結しても新しい String ができるだけで、元の文字列は変わりません。record もフィールドを書き換えられないので、写し側でできるのは要素の差し替えだけです。配列に入れるクラスを書き換えられない作りにしておくと、深いコピーを考える場面そのものが減ります。
2つの配列を結合する
Javaの配列には結合用のメソッドがないので、コピーを組み合わせて作ります。arraycopy版とStream版の両方を載せます。
import java.util.Arrays;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public class Concat {
// arraycopy 版:int[] 同士を結合する
static int[] concat(int[] a, int[] b) {
int[] result = Arrays.copyOf(a, a.length + b.length); // a をコピーしつつ長さを広げる
System.arraycopy(b, 0, result, a.length, b.length); // 後ろに b を入れる
return result;
}
// arraycopy 版:String[] などの参照型の配列を結合する
static <T> T[] concat(T[] a, T[] b) {
T[] result = Arrays.copyOf(a, a.length + b.length);
System.arraycopy(b, 0, result, a.length, b.length);
return result;
}
// arraycopy 版:3つ以上の int[] をまとめて結合する
static int[] concatAll(int[]... arrays) {
int total = 0;
for (int[] arr : arrays) {
total += arr.length;
}
int[] result = new int[total];
int pos = 0;
for (int[] arr : arrays) {
System.arraycopy(arr, 0, result, pos, arr.length);
pos += arr.length;
}
return result;
}
public static void main(String[] args) {
int[] a = {1, 2, 3};
int[] b = {4, 5};
String[] x = {"A", "B"};
String[] y = {"C"};
System.out.println("arraycopy版 int[] : " + Arrays.toString(concat(a, b)));
System.out.println("arraycopy版 String[] : " + Arrays.toString(concat(x, y)));
System.out.println("arraycopy版 3つ以上 : " + Arrays.toString(concatAll(a, b, new int[]{6})));
// Stream 版
int[] s1 = IntStream.concat(Arrays.stream(a), Arrays.stream(b)).toArray();
String[] s2 = Stream.concat(Arrays.stream(x), Arrays.stream(y)).toArray(String[]::new);
int[] s3 = Stream.of(a, b, new int[]{6}).flatMapToInt(Arrays::stream).toArray();
System.out.println("Stream版 int[] : " + Arrays.toString(s1));
System.out.println("Stream版 String[] : " + Arrays.toString(s2));
System.out.println("Stream版 3つ以上 : " + Arrays.toString(s3));
System.out.println("元の a と b : " + Arrays.toString(a) + " " + Arrays.toString(b));
}
}
実行結果:
arraycopy版 int[] : [1, 2, 3, 4, 5]
arraycopy版 String[] : [A, B, C]
arraycopy版 3つ以上 : [1, 2, 3, 4, 5, 6]
Stream版 int[] : [1, 2, 3, 4, 5]
Stream版 String[] : [A, B, C]
Stream版 3つ以上 : [1, 2, 3, 4, 5, 6]
元の a と b : [1, 2, 3] [4, 5]
arraycopy版は、Arrays.copyOf で1つ目の配列を写しながら長さを広げ、空いた後ろの部分へ System.arraycopy で2つ目を入れる、という2行でできています。元の a と b は変わりません。
| 項目 | arraycopy版 | Stream版 |
|---|---|---|
| 行数 | 2行(メソッドにまとめると呼び出しは1行) | 1行 |
| 基本型の配列 | 型ごとにメソッドが必要 | IntStream.concat など型ごとのクラスを使う |
| 参照型の配列 | ジェネリクスで1つにまとめられる | Stream.concat と toArray(String[]::new) |
| 3つ以上の結合 | 合計の長さを数えてから順に入れる | Stream.of(...) と flatMapToInt |
| 向いている場面 | 回数の多い処理、位置を細かく決めたい処理 | 結合のあとに絞り込みや並び替えを続ける処理 |
結合した配列の要素も浅いコピーです。オブジェクト配列を結合した場合、結果の配列と元の配列は同じオブジェクトを指します。
要素の追加・削除・差し込みもコピーで書ける
配列は作ったあとに長さを変えられないので、要素を増やしたり減らしたりするときも、新しい配列へのコピーで表します。
import java.util.Arrays;
public class AddRemove {
public static void main(String[] args) {
int[] src = {10, 20, 30, 40, 50};
// 末尾に1つ追加する
int[] added = Arrays.copyOf(src, src.length + 1);
added[src.length] = 60;
System.out.println("末尾に追加 : " + Arrays.toString(added));
// インデックス2の要素を取り除く
int index = 2;
int[] removed = new int[src.length - 1];
System.arraycopy(src, 0, removed, 0, index);
System.arraycopy(src, index + 1, removed, index, src.length - index - 1);
System.out.println("index 2 を取り除く: " + Arrays.toString(removed));
// インデックス2の位置に 25 を差し込む
int[] inserted = new int[src.length + 1];
System.arraycopy(src, 0, inserted, 0, index);
inserted[index] = 25;
System.arraycopy(src, index, inserted, index + 1, src.length - index);
System.out.println("index 2 に差し込む: " + Arrays.toString(inserted));
// 同じ配列の中で、要素を1つ後ろへずらす
int[] shift = {1, 2, 3, 4, 5};
System.arraycopy(shift, 0, shift, 1, 4);
System.out.println("同じ配列でずらす : " + Arrays.toString(shift));
// 先頭3件、末尾2件を取り出す
System.out.println("先頭3件 : " + Arrays.toString(Arrays.copyOf(src, 3)));
System.out.println("末尾2件 : " + Arrays.toString(Arrays.copyOfRange(src, src.length - 2, src.length)));
}
}
実行結果:
末尾に追加 : [10, 20, 30, 40, 50, 60]
index 2 を取り除く: [10, 20, 40, 50]
index 2 に差し込む: [10, 20, 25, 30, 40, 50]
同じ配列でずらす : [1, 1, 2, 3, 4]
先頭3件 : [10, 20, 30]
末尾2件 : [40, 50]
System.arraycopy は、コピー元とコピー先に同じ配列を指定できます。「同じ配列でずらす」の結果のように、範囲が重なっていても、いったん別の場所に写してからコピーしたのと同じ結果になります。
追加や削除を何度も行うなら、配列より ArrayList のほうが向いています。 配列と List の間のコピーは次のように書けます。
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class ToList {
public static void main(String[] args) {
String[] names = {"佐藤", "鈴木"};
// 配列 → 追加や削除ができる List へコピー
List<String> list = new ArrayList<>(Arrays.asList(names));
list.add("高橋");
// List → 配列へコピー
String[] back = list.toArray(new String[0]);
System.out.println("元の配列 : " + Arrays.toString(names));
System.out.println("List : " + list);
System.out.println("新しい配列: " + Arrays.toString(back));
}
}
実行結果:
元の配列 : [佐藤, 鈴木]
List : [佐藤, 鈴木, 高橋]
新しい配列: [佐藤, 鈴木, 高橋]
コピーできたかを確かめる方法
コピーの結果を確認するときは、比べ方によって意味が変わります。
import java.util.Arrays;
public class Compare {
public static void main(String[] args) {
int[] src = {1, 2, 3};
int[] copy = src.clone();
System.out.println("copy == src : " + (copy == src));
System.out.println("copy.equals(src) : " + copy.equals(src));
System.out.println("Arrays.equals : " + Arrays.equals(copy, src));
int[][] grid = { {1, 2}, {3, 4} };
int[][] deep = Arrays.stream(grid).map(int[]::clone).toArray(int[][]::new);
System.out.println("2次元 Arrays.equals : " + Arrays.equals(deep, grid));
System.out.println("2次元 Arrays.deepEquals : " + Arrays.deepEquals(deep, grid));
}
}
実行結果:
copy == src : false
copy.equals(src) : false
Arrays.equals : true
2次元 Arrays.equals : false
2次元 Arrays.deepEquals : true
| 比べ方 | 何を比べるか | 使いどころ |
|---|---|---|
copy == src |
同じ配列かどうか | 別の配列になっているかの確認(false ならコピーできている) |
copy.equals(src) |
== と同じ |
配列の中身の比較には使わない |
Arrays.equals(copy, src) |
1次元の中身が同じか | 1次元配列の内容確認 |
Arrays.deepEquals(copy, src) |
入れ子の中身まで同じか | 多次元配列の内容確認 |
「== が false」かつ「Arrays.equals が true」なら、中身が同じ別の配列ができています。 深いコピーにした2次元配列は行の配列が別物になるので、Arrays.equals では false、Arrays.deepEquals で true になります。中身を目で見たいときは、1次元は Arrays.toString、多次元は Arrays.deepToString を使います。
つまずきやすい点と直し方
| 状況 | 直す前 | 直した後 |
|---|---|---|
| 写しを変えたら元も変わった(1次元) | int[] copy = src; |
int[] copy = src.clone(); |
| 写しを変えたら元も変わった(2次元) | int[][] copy = grid.clone(); |
Arrays.stream(grid).map(int[]::clone).toArray(int[][]::new) |
| 範囲の最後の1件が入らない | Arrays.copyOfRange(src, 1, 3) で1〜3のつもり |
Arrays.copyOfRange(src, 1, 4)(終了はその手前まで) |
arraycopy で例外になる |
コピー先を new int[3] にして5個コピー |
コピー先を必要な長さで用意する |
| 広げた配列で NullPointerException | Arrays.copyOf(names, 4) の直後に names[3].length() |
値を入れてから使う |
arraycopy の結果を変数で受けようとした |
int[] d = System.arraycopy(...) |
先に int[] d = new int[n]; を作る |
| 中身が同じなのに false になる | copy.equals(src) |
Arrays.equals(copy, src) |
確認用のチェックリスト
- 代入(
=)ではなく、4つの方法のどれかでコピーしている - 要素が配列やオブジェクトのとき、浅いコピーでよいか、深いコピーが必要かを決めている
copyOfRangeの終了位置が「その手前まで」であることを踏まえているSystem.arraycopyのコピー先を、必要な長さで用意している- 参照型の配列を広げたあと、null の要素に値を入れている
- コピーの確認に
Arrays.equalsまたはArrays.deepEqualsを使っている
よくある質問
Q. cloneとArrays.copyOfは、どちらを使えばよいですか?
A. 全体を同じ長さで写すなら、どちらでも同じ結果になります。短く書けるのは clone() です。長さを変えたい場合や、先頭の一部だけがほしい場合は Arrays.copyOf を選びます。どちらも浅いコピーである点は同じです。
Q. System.arraycopyはどんなときに使いますか?
A. コピー先の配列をすでに持っていて、その途中の位置へ書き込みたいときです。配列の結合、要素の差し込み、同じ配列の中で要素をずらす処理に向いています。新しい配列を作るだけなら、clone() や Arrays.copyOf のほうが引数が少なく読みやすくなります。
Q. 2次元配列をcloneしたのに、元の配列まで変わるのはなぜですか?
A. clone() が新しく作るのは外側の配列だけで、中の行の配列は元と同じものを指しているためです。行ごとに clone() する深いコピーにすると、写しと元が独立します。この記事の「深いコピーの書き方」のコードをそのまま使えます。
Q. 配列を結合するメソッドはありますか?
A. Javaの標準ライブラリには、配列同士を結合する専用のメソッドはありません。Arrays.copyOf で広げてから System.arraycopy で後ろに入れる方法か、IntStream.concat・Stream.concat を使う方法で結合します。どちらも元の配列は変わりません。
まとめ
- 配列のコピーは
clone()・Arrays.copyOf・Arrays.copyOfRange・System.arraycopyの4つから、長さと範囲で選ぶ int[] b = a;は代入であり、コピーではない。片方を変えるともう片方も変わるArrays.copyOfは長さを変えられ、余った部分は 0・false・null で埋まるcopyOfRangeの終了位置は「その手前まで」、System.arraycopyはコピー先を自分で用意する- 4つの方法はどれも浅いコピー。2次元配列やオブジェクト配列は、要素ごとにコピーして深いコピーにする
- 配列の結合は
Arrays.copyOfとSystem.arraycopyの2行、またはIntStream.concat・Stream.concatの1行で書ける - 確認には
==(別の配列か)とArrays.equals・Arrays.deepEquals(中身が同じか)を使い分ける
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java配列コピー4つの方法|copyOf・arraycopy・clone」
株式会社RJCでは、Javaの配列コピーのような基礎を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。