Arrays.copyOf / copyOfRange — 配列操作と拡張
配列を「丸ごとコピー」「サイズ変更」「一部だけ取り出す」を、安全に簡潔に書けるのが Arrays.copyOf と Arrays.copyOfRange。初心者がつまずきやすい「インデックスの含まれる/含まれない」「サイズを超えるとどうなる」を、例とテンプレートで分かりやすく整理します。
基本の考え方
- Arrays.copyOf(original, newLength): 配列を新しい長さでコピー。先頭から
newLength要素分だけ新配列に詰める。足りない分は型の初期値で埋まる(intなら0、booleanならfalse、参照型ならnull)。 - Arrays.copyOfRange(original, from, to):
from(含む)〜to(含まない)の範囲をコピー。to - from要素の新配列になる。範囲が元配列の末尾を越えた不足分は初期値で埋まる。 - 不変な性質: 元配列は変更されない(新しい配列を返す)。
- 型を保つ: 元の配列の型に合わせた新配列が返る(プリミティブ・参照型ともにOK)。
すぐ試せる基本例
1) サイズを変えてコピー(copyOf)
import java.util.Arrays;
int[] a = {1, 2, 3};
// 3→5に拡張(不足分は0で埋まる)
int[] b = Arrays.copyOf(a, 5);
System.out.println(Arrays.toString(b)); // [1, 2, 3, 0, 0]
// 3→2に縮小(先頭2つだけ残る)
int[] c = Arrays.copyOf(a, 2);
System.out.println(Arrays.toString(c)); // [1, 2]
Java2) 範囲指定で取り出す(copyOfRange)
import java.util.Arrays;
int[] a = {10, 20, 30, 40, 50};
// [from=1, to=4) → 1,2,3番目を取得
int[] slice = Arrays.copyOfRange(a, 1, 4);
System.out.println(Arrays.toString(slice)); // [20, 30, 40]
Java3) 範囲外まで取り出すと…(不足分は初期値)
import java.util.Arrays;
int[] a = {1, 2, 3};
// to が配列長を越える(初期値補充)
int[] extended = Arrays.copyOfRange(a, 1, 6);
System.out.println(Arrays.toString(extended)); // [2, 3, 0, 0, 0]
Java参照型・文字列でも同じように使える
import java.util.Arrays;
String[] names = {"Tanaka", "Sato", "Ito"};
// 先頭2件だけコピー
String[] first2 = Arrays.copyOf(names, 2); // ["Tanaka", "Sato"]
// 1..3 を範囲コピー
String[] mid = Arrays.copyOfRange(names, 1, 3); // ["Sato", "Ito"]
// 長くコピー(不足は null)
String[] padded = Arrays.copyOf(names, 5); // ["Tanaka", "Sato", "Ito", null, null]
Java- 参照型の不足分は
nullになる点に注意。
よくある用途とレシピ
配列の「拡張」代わりに使う
int[] buf = new int[3];
buf[0]=1; buf[1]=2; buf[2]=3;
// 要素が増える見込みで拡張
buf = Arrays.copyOf(buf, 6); // 末尾に余白ができる(0で埋まる)
Java部分抽出(スライス)
double[] data = {0.5, 1.2, 3.4, 5.6, 7.8};
double[] window = Arrays.copyOfRange(data, 2, 5); // [3.4, 5.6, 7.8]
Java片側だけ欲しい(先頭N/末尾N)
int[] a = {1,2,3,4,5};
// 先頭3件
int[] head = Arrays.copyOf(a, 3);
// 末尾3件
int n = 3;
int[] tail = Arrays.copyOfRange(a, Math.max(0, a.length - n), a.length);
Java落とし穴と回避策
- インデックスの意味違い(fromは含む、toは含まない):
- よくある誤りは「toも含む」と思い込むこと。常に
toは「含まない」ので、欲しい最後の位置はlastIndex + 1を指定する。
- よくある誤りは「toも含む」と思い込むこと。常に
- 負のインデックスや from > to:
from < 0やfrom > toは例外。範囲を必ず検証する。toが大きすぎる場合は初期値で埋まるが、fromが大きすぎると長さ0や例外になりやすい。
- 巨大コピーのメモリ負荷:
- 大きく拡張するとヒープを圧迫。必要サイズを見積もる、分割して処理するなどの対策を。
- 深いコピーが必要なとき:
- 2次元配列や内部参照を持つ配列の「完全コピー」が必要なら、要素ごとにコピーする。
copyOfは「1階層」のコピー。
- 2次元配列や内部参照を持つ配列の「完全コピー」が必要なら、要素ごとにコピーする。
テンプレート集(そのまま使える形)
- サイズ指定コピー
T[] dst = Arrays.copyOf(src, newLength);
Java- 範囲コピー(from 含む、to 含まない)
T[] dst = Arrays.copyOfRange(src, from, to);
Java- 末尾N件の取得
int n = ...;
T[] tail = Arrays.copyOfRange(src, Math.max(0, src.length - n), src.length);
Java- 安全なスライス(境界チェック付き)
int from = ...; int to = ...;
from = Math.max(0, from);
to = Math.min(src.length, to);
if (from > to) to = from;
T[] slice = Arrays.copyOfRange(src, from, to);
Java例題で身につける
例題1: CSV行の先頭3列だけ抜き出し
import java.util.Arrays;
String[] cols = "A,B,C,D,E".split(",");
String[] first3 = Arrays.copyOf(cols, 3);
System.out.println(Arrays.toString(first3)); // [A, B, C]
Java例題2: ログのローリングバッファ(末尾N件を取得)
import java.util.Arrays;
String[] logs = {"L1","L2","L3","L4","L5"};
int n = 3;
String[] recent = Arrays.copyOfRange(logs, Math.max(0, logs.length - n), logs.length);
System.out.println(Arrays.toString(recent)); // [L3, L4, L5]
Java例題3: 2次元配列の「浅いコピー」と「深いコピー」
int[][] grid = {{1,2},{3,4}};
// 浅いコピー(行配列への参照は共有される)
int[][] shallow = Arrays.copyOf(grid, grid.length);
// 深いコピー(各行もコピー)
int[][] deep = new int[grid.length][];
for (int i = 0; i < grid.length; i++) {
deep[i] = Arrays.copyOf(grid[i], grid[i].length);
}
Javaまとめ
copyOfは「サイズ指定で先頭からコピー」、copyOfRangeは「範囲指定(from含む/to含まない)」で部分抽出。足りない分は型の初期値で埋まる。- 参照型は不足分が
null、プリミティブは型の初期値になる。範囲とサイズの境界を正しく扱えば、安全に配列の拡張・スライスが書ける。 - 2次元以上の構造では必要に応じて「深いコピー」を選び、メモリ負荷と境界チェックに気を配ると失敗しない。
