- JAVA
Java配列の初期化・コピー・Arrays.sort|逆順と浅いコピーの注意点
Javaの配列とは、同じ型の値を決まった数だけ並べて持つ入れ物です。 初期化はnew int[5]か{1, 2, 3}、コピーはclone()かArrays.copyOf、並べ替えはArrays.sortが基本です。ただし、int[]をCollections.reverseOrder()で逆順にしようとするとコンパイルエラーになり、2次元配列をclone()すると中身が共有される(浅いコピー)という、つまずきやすい点があります。
この記事では、初期化5パターン・コピー4手法・ソート8通りを、それぞれ同じデータで動かした実行結果を掲載します。逆順ソートのコンパイルエラー、2次元配列の浅いコピー、ArrayIndexOutOfBoundsExceptionも実際のエラー文で再現し、直し方を示します。コードはすべてOpenJDK 21.0.10で実行し、出力をそのまま貼り付けています。
Java配列の早見表(初期化・コピー・ソート)
やりたいことから書き方を探せる一覧です。 「結果」列はこの記事の実行結果から抜き出しています。
| やりたいこと | 書き方 | 結果 |
|---|---|---|
| サイズだけ決めて作る | int[] a = new int[5]; |
[0, 0, 0, 0, 0] |
| 値を並べて作る | int[] a = {1, 2, 3}; |
[1, 2, 3] |
| 宣言の後で値を並べて代入 | a = new int[]{4, 5, 6}; |
[4, 5, 6] |
| 全部同じ値で埋める | Arrays.fill(a, 7); |
[7, 7, 7, 7, 7] |
| 連番で作る | IntStream.rangeClosed(1, 5).toArray() |
[1, 2, 3, 4, 5] |
| 番号から計算して埋める | Arrays.setAll(a, i -> i * i); |
[0, 1, 4, 9, 16] |
| 中身を表示 | Arrays.toString(a) |
[1, 2, 3] |
| 2次元配列を表示 | Arrays.deepToString(g) |
[[1, 2, 3], [4, 5, 6]] |
| まるごとコピー | a.clone() / Arrays.copyOf(a, a.length) |
別の配列になる |
| 一部をコピー | Arrays.copyOfRange(a, 1, 4) |
1〜3番目(4は含まない) |
| 既存の配列へコピー | System.arraycopy(src, 0, dst, 0, n) |
dstに上書き |
| 長さを変えてコピー | Arrays.copyOf(a, 7) |
足りない分は0 |
| 昇順に並べる | Arrays.sort(a); |
[1, 3, 5, 7, 9] |
逆順(Integer[]) |
Arrays.sort(b, Collections.reverseOrder()); |
[9, 7, 5, 3, 1] |
逆順(int[]のまま) |
IntStream.of(...).boxed().sorted(Comparator.reverseOrder())… |
[9, 7, 5, 3, 1] |
| 一部だけ並べる | Arrays.sort(a, 1, 4); |
1〜3番目だけ昇順 |
| 条件を指定して並べる | Arrays.sort(items, Comparator.comparing(…)) |
オブジェクト配列のみ |
| 2次元配列の深いコピー | Arrays.stream(g).map(int[]::clone).toArray(int[][]::new) |
行も別になる |
| 中身が同じか比べる | Arrays.equals(a, b) / Arrays.deepEquals(g, h) |
==は同じ配列かの比較 |
| 探す(ソート済み) | Arrays.binarySearch(a, 7) |
見つかった位置 |
つまずきやすい3点を先にまとめます。
| よくある書き方 | 起きること | 直し方 |
|---|---|---|
int[] b = a;でコピーしたつもり |
同じ配列を指すだけ。bを変えるとaも変わる |
a.clone()かArrays.copyOf |
Arrays.sort(intArray, Collections.reverseOrder()) |
コンパイルエラー(intはComparatorで比べられない) |
Integer[]にする/Streamで並べる/昇順後に反転 |
int[][] c = g.clone(); |
行(中の配列)は共有される。c[0][0]を変えるとgも変わる |
行ごとにclone()する |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java配列の初期化・コピー・Arrays.sort|逆順と浅いコピーの注意点」
配列を初期化する5つの方法
配列の作り方は、サイズだけ決めるnew int[5]、値を並べる{...}とnew int[]{...}、同じ値で埋めるArrays.fill、計算で作るStream(IntStream)の5パターンです。 同じプログラムで並べて動かします。
import java.util.Arrays;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public class ArrayInit {
public static void main(String[] args) {
// 1. サイズだけ指定(既定値で埋まる)
int[] a1 = new int[5];
// 2. 初期値を並べる(宣言と同時のときだけ)
int[] a2 = {1, 2, 3};
// 3. new 型[]{...}(代入・引数でも使える)
int[] a3;
a3 = new int[]{4, 5, 6};
// 4. Arrays.fill で同じ値
int[] a4 = new int[5];
Arrays.fill(a4, 7);
// 5. Stream / IntStream で生成
int[] a5 = IntStream.rangeClosed(1, 5).toArray();
int[] a6 = IntStream.iterate(10, x -> x + 10).limit(5).toArray();
String[] a7 = Stream.of("a", "b", "c").map(String::toUpperCase).toArray(String[]::new);
int[] a8 = new int[5];
Arrays.setAll(a8, i -> i * i);
System.out.println("new int[5] : " + Arrays.toString(a1));
System.out.println("{1, 2, 3} : " + Arrays.toString(a2));
System.out.println("new int[]{4, 5, 6} : " + Arrays.toString(a3));
System.out.println("Arrays.fill(a, 7) : " + Arrays.toString(a4));
System.out.println("IntStream.rangeClosed: " + Arrays.toString(a5));
System.out.println("IntStream.iterate : " + Arrays.toString(a6));
System.out.println("Stream.toArray : " + Arrays.toString(a7));
System.out.println("Arrays.setAll : " + Arrays.toString(a8));
System.out.println("length : " + a1.length);
System.out.println("println(配列) そのまま: " + a2);
}
}
java ArrayInit.javaで実行した結果(Java 21.0.10。端末はUTF-8):
new int[5] : [0, 0, 0, 0, 0]
{1, 2, 3} : [1, 2, 3]
new int[]{4, 5, 6} : [4, 5, 6]
Arrays.fill(a, 7) : [7, 7, 7, 7, 7]
IntStream.rangeClosed: [1, 2, 3, 4, 5]
IntStream.iterate : [10, 20, 30, 40, 50]
Stream.toArray : [A, B, C]
Arrays.setAll : [0, 1, 4, 9, 16]
length : 5
println(配列) そのまま: [I@31c88ec8
配列をそのままprintlnすると[I@31c88ec8のような「型とハッシュ値」が表示されます(後半の値は実行ごとに変わります)。中身を見るにはArrays.toStringを使います。長さはlength(メソッドではないので()は付けない)で取得します。
| パターン | 書き方 | 使いどころ |
|---|---|---|
| ① サイズ指定 | new int[5] |
後からループで値を入れる |
| ② 初期値の列挙 | int[] a = {1, 2, 3}; |
値が決まっている。宣言と同時のときだけ書ける |
③ new+列挙 |
new int[]{1, 2, 3} |
宣言後の代入、メソッドの引数、return |
④ Arrays.fill |
Arrays.fill(a, -1) |
「未設定」を表す値で埋める |
| ⑤ Stream | IntStream.range(0, n).toArray()、Arrays.setAll |
連番・計算式で作る |
宣言後に{...}だけで代入するとコンパイルエラー
{1, 2, 3}の書き方(配列初期化子)は、宣言と同時のときしか使えません。 宣言した後に代入するとエラーになります。
public class InitErr {
public static void main(String[] args) {
int[] a;
a = {1, 2, 3};
}
}
java InitErr.javaの結果(Java 21.0.10):
InitErr.java:4: error: illegal start of expression
a = {1, 2, 3};
^
InitErr.java:4: error: not a statement
a = {1, 2, 3};
^
InitErr.java:4: error: ';' expected
a = {1, 2, 3};
^
3 errors
error: compilation failed
a = new int[]{1, 2, 3};とnew int[]を付ければ通ります。 メソッドの引数に直接渡すとき(sum(new int[]{1, 2, 3}))も同じです。
型ごとの既定値(new int[5]で入る値)
new 型[n]で作った配列は、型ごとの既定値で埋まります。 すべての型で確認しました。
import java.util.Arrays;
public class Defaults {
public static void main(String[] args) {
System.out.println("byte : " + Arrays.toString(new byte[2]));
System.out.println("short : " + Arrays.toString(new short[2]));
System.out.println("int : " + Arrays.toString(new int[2]));
System.out.println("long : " + Arrays.toString(new long[2]));
System.out.println("float : " + Arrays.toString(new float[2]));
System.out.println("double : " + Arrays.toString(new double[2]));
char[] c = new char[2];
System.out.println("char : 文字コード " + (int) c[0] + "(見えない文字)");
System.out.println("boolean : " + Arrays.toString(new boolean[2]));
System.out.println("String : " + Arrays.toString(new String[2]));
System.out.println("Integer : " + Arrays.toString(new Integer[2]));
}
}
実行結果(Java 21.0.10):
byte : [0, 0]
short : [0, 0]
int : [0, 0]
long : [0, 0]
float : [0.0, 0.0]
double : [0.0, 0.0]
char : 文字コード 0(見えない文字)
boolean : [false, false]
String : [null, null]
Integer : [null, null]
| 型 | 既定値 | 注意 |
|---|---|---|
byte・short・int・long |
0 |
|
float・double |
0.0 |
|
char |
文字コード0の文字 | 画面には見えない。空白(スペース)ではない |
boolean |
false |
|
String・Integerなどの参照型 |
null |
そのままメソッドを呼ぶとNullPointerException |
String[]やInteger[]はnullで埋まるので、Arrays.fill(names, "")などで先に埋めておくと、後の処理でnullチェックが要らなくなります。
配列をコピーする4つの方法と「=」代入の違い
int[] b = a;はコピーではなく、同じ配列に別の名前を付けるだけです。 別の配列として複製するには、clone()・System.arraycopy・Arrays.copyOf・Arrays.copyOfRangeの4つを使います。コピーした後で元の配列の先頭を99に変え、影響が出るかを確かめます。
import java.util.Arrays;
public class ArrayCopy {
public static void main(String[] args) {
int[] src = {1, 2, 3, 4, 5};
int[] alias = src; // 代入(同じ配列を指すだけ)
int[] c1 = src.clone(); // clone
int[] c2 = new int[src.length];
System.arraycopy(src, 0, c2, 0, src.length); // System.arraycopy
int[] c3 = Arrays.copyOf(src, src.length); // Arrays.copyOf
int[] c4 = Arrays.copyOfRange(src, 1, 4); // Arrays.copyOfRange [1, 4)
int[] longer = Arrays.copyOf(src, 7); // 長くすると 0 で埋まる
int[] shorter = Arrays.copyOf(src, 3); // 短くすると切り詰め
src[0] = 99;
System.out.println("src : " + Arrays.toString(src));
System.out.println("= 代入 : " + Arrays.toString(alias) + " 同じ配列? " + (alias == src));
System.out.println("clone : " + Arrays.toString(c1));
System.out.println("arraycopy : " + Arrays.toString(c2));
System.out.println("copyOf : " + Arrays.toString(c3));
System.out.println("copyOfRange : " + Arrays.toString(c4));
System.out.println("copyOf(7) : " + Arrays.toString(longer));
System.out.println("copyOf(3) : " + Arrays.toString(shorter));
// arraycopy は同じ配列内のずらしにも使える(重なりOK)
int[] shift = {1, 2, 3, 4, 5};
System.arraycopy(shift, 0, shift, 1, 4);
System.out.println("右に1つずらす: " + Arrays.toString(shift));
// 要素の追加(配列は長さ固定なので作り直す)
int[] added = Arrays.copyOf(c1, c1.length + 1);
added[added.length - 1] = 6;
System.out.println("末尾に追加 : " + Arrays.toString(added));
try {
System.arraycopy(src, 0, new int[3], 0, 5);
} catch (Exception e) {
System.out.println("arraycopy 範囲外: " + e);
}
}
}
実行結果(Java 21.0.10):
src : [99, 2, 3, 4, 5]
= 代入 : [99, 2, 3, 4, 5] 同じ配列? true
clone : [1, 2, 3, 4, 5]
arraycopy : [1, 2, 3, 4, 5]
copyOf : [1, 2, 3, 4, 5]
copyOfRange : [2, 3, 4]
copyOf(7) : [1, 2, 3, 4, 5, 0, 0]
copyOf(3) : [1, 2, 3]
右に1つずらす: [1, 1, 2, 3, 4]
末尾に追加 : [1, 2, 3, 4, 5, 6]
arraycopy 範囲外: java.lang.ArrayIndexOutOfBoundsException: arraycopy: last destination index 5 out of bounds for int[3]
「=」代入だけが99の影響を受け、4つのコピー方法はどれも元の値1のままでした。 4手法の違いは次のとおりです。
| 方法 | コピー先 | 長さの変更 | 範囲指定 | 向いている場面 |
|---|---|---|---|---|
a.clone() |
新しく作る | できない | できない | まるごとコピーの一番短い書き方 |
Arrays.copyOf(a, n) |
新しく作る | できる(足りない分は既定値) | 先頭からn個 | 長さを変えたい・要素を追加したい |
Arrays.copyOfRange(a, from, to) |
新しく作る | できる | できる(toは含まない) |
一部だけ切り出す |
System.arraycopy(src, sPos, dst, dPos, n) |
既存の配列に上書き | ― | できる | 確保済みの配列へ詰める、同じ配列内でずらす |
int[] b = a; |
コピーしない | ― | ― | 同じ配列を別名で扱うとき |
迷ったらclone()かArrays.copyOfがおすすめです。
配列は作った後に長さを変えられません。 要素を追加したいときは上の「末尾に追加」のように作り直すか、最初からArrayListを使います。何度も追加・削除するならArrayListの方が書きやすく、選び方は「Java List Set Map 使い分け」の記事で解説しています。
Arrays.sortで並べ替える(昇順・逆順・範囲・条件指定)
Arrays.sort(配列)で昇順に並べ替えられます。 逆順にするにはComparator(比べ方を表すオブジェクト)を渡しますが、Comparatorを使えるのはInteger[]やString[]などのオブジェクトの配列だけです。8通りの並べ方を一度に動かします。
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.stream.IntStream;
public class ArraySort {
public static void main(String[] args) {
// 1. 昇順
int[] nums = {5, 3, 9, 1, 7};
Arrays.sort(nums);
System.out.println("昇順 int[] : " + Arrays.toString(nums));
// 2. 逆順(Integer[] にして reverseOrder)
Integer[] boxed = {5, 3, 9, 1, 7};
Arrays.sort(boxed, Collections.reverseOrder());
System.out.println("逆順 Integer[] : " + Arrays.toString(boxed));
// 3. 逆順(int[] のまま Stream で)
int[] desc = IntStream.of(5, 3, 9, 1, 7).boxed()
.sorted(Comparator.reverseOrder())
.mapToInt(Integer::intValue).toArray();
System.out.println("逆順 Stream : " + Arrays.toString(desc));
// 4. 逆順(昇順にしてから前後を入れ替え)
int[] rev = {5, 3, 9, 1, 7};
Arrays.sort(rev);
for (int i = 0, j = rev.length - 1; i < j; i++, j--) {
int tmp = rev[i]; rev[i] = rev[j]; rev[j] = tmp;
}
System.out.println("逆順 sort+入れ替え : " + Arrays.toString(rev));
// 5. 範囲指定(インデックス1〜3だけ並べる。終わりは含まない)
int[] part = {9, 8, 7, 6, 5, 4};
Arrays.sort(part, 1, 4);
System.out.println("範囲 sort(a, 1, 4) : " + Arrays.toString(part));
// 6. 文字列(辞書順・大文字小文字無視・長さ順)
String[] words = {"banana", "Apple", "cherry", "Fig"};
String[] w1 = words.clone();
Arrays.sort(w1);
String[] w2 = words.clone();
Arrays.sort(w2, String.CASE_INSENSITIVE_ORDER);
String[] w3 = words.clone();
Arrays.sort(w3, Comparator.comparingInt(String::length).thenComparing(Comparator.naturalOrder()));
System.out.println("String 辞書順 : " + Arrays.toString(w1));
System.out.println("String 大小無視 : " + Arrays.toString(w2));
System.out.println("String 長さ→辞書順 : " + Arrays.toString(w3));
// 7. オブジェクト配列を複数キーで
record Item(String name, int stock) {}
Item[] items = {new Item("pen", 30), new Item("note", 5), new Item("clip", 30), new Item("tape", 12)};
Arrays.sort(items, Comparator.comparingInt(Item::stock).reversed().thenComparing(Item::name));
System.out.println("record 在庫降順→名前 : " + Arrays.toString(items));
// 8. parallelSort
int[] big = new java.util.Random(42).ints(10, 0, 100).toArray();
Arrays.parallelSort(big);
System.out.println("parallelSort : " + Arrays.toString(big));
}
}
実行結果(Java 21.0.10):
昇順 int[] : [1, 3, 5, 7, 9]
逆順 Integer[] : [9, 7, 5, 3, 1]
逆順 Stream : [9, 7, 5, 3, 1]
逆順 sort+入れ替え : [9, 7, 5, 3, 1]
範囲 sort(a, 1, 4) : [9, 6, 7, 8, 5, 4]
String 辞書順 : [Apple, Fig, banana, cherry]
String 大小無視 : [Apple, banana, cherry, Fig]
String 長さ→辞書順 : [Fig, Apple, banana, cherry]
record 在庫降順→名前 : [Item[name=clip, stock=30], Item[name=pen, stock=30], Item[name=tape, stock=12], Item[name=note, stock=5]]
parallelSort : [5, 18, 19, 25, 30, 48, 63, 70, 84, 93]
出力のポイント:
- 範囲指定
sort(a, 1, 4)は、インデックス1・2・3だけが6, 7, 8に並び、0番目の9と4番目以降はそのままです(終わりの4は含まない)。 - 文字列の辞書順は「大文字が先」です(文字コード順のため
Figがbananaより前)。大文字小文字を区別しないならString.CASE_INSENSITIVE_ORDERを渡します。 - 同じ在庫数の
clipとpenは、thenComparingで名前順になりました。複数キーはthenComparingでつなげます。
int[]にCollections.reverseOrder()を渡すとコンパイルエラー
int[]はComparator付きのArrays.sortを使えません。 実際にコンパイルすると次のエラーになります。
import java.util.Arrays;
import java.util.Collections;
public class SortErr {
public static void main(String[] args) {
int[] nums = {5, 3, 9};
Arrays.sort(nums, Collections.reverseOrder());
}
}
java SortErr.javaの結果(Java 21.0.10。候補メソッドの一覧は途中を省略):
SortErr.java:7: error: no suitable method found for sort(int[],Comparator<Object>)
Arrays.sort(nums, Collections.reverseOrder());
^
method Arrays.sort(int[]) is not applicable
(actual and formal argument lists differ in length)
(中略:sort(long[]) から sort(Object[],int,int) まで同様)
method Arrays.<T#1>sort(T#1[],Comparator<? super T#1>) is not applicable
(inference variable T#1 has incompatible bounds
equality constraints: int
upper bounds: Object)
(以下略)
1 error
error: compilation failed
原因は、Comparator<T>のTにはintのような基本型(プリミティブ型)を入れられないことです。 エラー文のequality constraints: intとupper bounds: Objectが「Tはintでなければならないが、Objectの仲間でなければならない」という矛盾を表しています。直し方は3つです。
| 直し方 | 書き方 | 向いている場面 |
|---|---|---|
① Integer[]で持つ |
Arrays.sort(boxed, Collections.reverseOrder()) |
最初からInteger[]で扱える |
| ② Streamで並べ直す | IntStream.of(a).boxed().sorted(Comparator.reverseOrder()).mapToInt(Integer::intValue).toArray() |
int[]のまま結果がほしい。1行で書きたい |
| ③ 昇順にしてから反転 | Arrays.sort(a);の後に前後を入れ替えるループ |
件数が多く、ボクシング(int→Integerの変換)を避けたい |
件数が少なければ②が手軽です。 大量のintを逆順にするなら、Integerオブジェクトを作らない③が向いています。ほかに「全要素に-1を掛けて昇順にして戻す」方法もありますが、Integer.MIN_VALUEが含まれると符号を反転できない点に注意が要ります。
sortとparallelSortの違い
Arrays.parallelSortは、配列を分割して複数のCPUコアで並べ替えるメソッドです(Java 8以降)。 結果はsortと同じです。1,000万件のランダムなint配列(new Random(1).ints(n)で作成)を、clone()した同じデータで3回ずつ並べ替え、System.nanoTime()で測りました。
実行結果(Java 21.0.10、このマシン):
1回目 sort 3001ms / parallelSort 2235ms / 結果一致=true
2回目 sort 1130ms / parallelSort 1131ms / 結果一致=true
3回目 sort 1274ms / parallelSort 1199ms / 結果一致=true
CPUコア数: 2
2コアのこの環境では、ウォームアップ後はほぼ同じ速さでした。 parallelSortはコア数が多く件数が大きいほど効果が出やすく、Javadocでも一定の大きさより小さい配列は通常のArrays.sortで処理すると説明されています。まずはArrays.sortを使い、大きな配列で時間が気になったら実際の環境で両方を測るのがおすすめです(簡易計測です)。どちらもオブジェクト配列では安定ソート(同じ値の順番を保つ)です。
2次元配列の初期化と浅いコピー(cloneの注意点)
Javaの2次元配列は「配列の配列」です。 そのため、clone()で複製されるのは外側の配列だけで、中の行(1次元配列)は元と共有されます。これを浅いコピー(シャローコピー)と呼びます。初期化からコピーまで動かします。
import java.util.Arrays;
public class Array2D {
public static void main(String[] args) {
// 初期化3パターン
int[][] g1 = new int[2][3];
int[][] g2 = {{1, 2, 3}, {4, 5, 6}};
int[][] jag = new int[3][]; // 行ごとに長さが違う(ジャグ配列)
for (int i = 0; i < jag.length; i++) {
jag[i] = new int[i + 1];
Arrays.fill(jag[i], i + 1);
}
System.out.println("new int[2][3] : " + Arrays.deepToString(g1));
System.out.println("{{..},{..}} : " + Arrays.deepToString(g2));
System.out.println("ジャグ配列 : " + Arrays.deepToString(jag));
System.out.println("toString だと : " + Arrays.toString(g2).replaceAll("@[0-9a-f]+", "@(ハッシュ値)"));
System.out.println("行数=" + g2.length + " 列数=" + g2[0].length);
// 浅いコピーの再現
int[][] src = {{1, 2}, {3, 4}};
int[][] shallow = src.clone();
shallow[0][0] = 99; // 中の配列は共有されている
shallow[1] = new int[]{7, 7}; // 行の差し替えは src に影響しない
System.out.println("clone 後 src : " + Arrays.deepToString(src));
System.out.println("clone 後 copy : " + Arrays.deepToString(shallow));
System.out.println("src[0] == shallow[0] : " + (src[0] == shallow[0]));
// 深いコピー(行ごとに clone)
int[][] src2 = {{1, 2}, {3, 4}};
int[][] deep = new int[src2.length][];
for (int i = 0; i < src2.length; i++) {
deep[i] = src2[i].clone();
}
deep[0][0] = 99;
System.out.println("deep 後 src2 : " + Arrays.deepToString(src2));
System.out.println("deep 後 copy : " + Arrays.deepToString(deep));
// Stream で1行の深いコピー
int[][] deep2 = Arrays.stream(src2).map(int[]::clone).toArray(int[][]::new);
System.out.println("Stream deep : " + Arrays.deepToString(deep2) + " 共有? " + (deep2[0] == src2[0]));
// 2次元配列のソート(2列目の降順)
int[][] rows = {{1, 30}, {2, 10}, {3, 20}};
Arrays.sort(rows, (a, b) -> Integer.compare(b[1], a[1]));
System.out.println("2列目で降順 : " + Arrays.deepToString(rows));
// 2次元の比較
int[][] x = {{1, 2}, {3, 4}};
int[][] y = {{1, 2}, {3, 4}};
System.out.println("equals=" + Arrays.equals(x, y) + " deepEquals=" + Arrays.deepEquals(x, y));
}
}
実行結果(Java 21.0.10):
new int[2][3] : [[0, 0, 0], [0, 0, 0]]
{{..},{..}} : [[1, 2, 3], [4, 5, 6]]
ジャグ配列 : [[1], [2, 2], [3, 3, 3]]
toString だと : [[I@(ハッシュ値), [I@(ハッシュ値)]
行数=2 列数=3
clone 後 src : [[99, 2], [3, 4]]
clone 後 copy : [[99, 2], [7, 7]]
src[0] == shallow[0] : true
deep 後 src2 : [[1, 2], [3, 4]]
deep 後 copy : [[99, 2], [3, 4]]
Stream deep : [[1, 2], [3, 4]] 共有? false
2列目で降順 : [[1, 30], [3, 20], [2, 10]]
equals=false deepEquals=true
cloneしたコピー側で[0][0]を99にしたら、元のsrcも99に変わりました。 一方、shallow[1] = new int[]{7, 7}のように行そのものを差し替えた変更は、元には影響していません。外側の配列は別物で、中の行だけが共有されているからです。
flowchart LR
src["src(外側の配列)"] --> r0["行0: [99, 2]"]
src --> r1["行1: [3, 4]"]
shallow["shallow = src.clone()"] --> r0
shallow --> n1["新しい行: [7, 7]"]
| コピー方法 | 外側 | 中の行 | copy[0][0] = 99の元への影響 |
|---|---|---|---|
int[][] c = g; |
共有 | 共有 | 変わる |
g.clone()/Arrays.copyOf(g, n) |
別 | 共有(浅いコピー) | 変わる |
行ごとにclone()(ループ) |
別 | 別(深いコピー) | 変わらない |
Arrays.stream(g).map(int[]::clone).toArray(int[][]::new) |
別 | 別(深いコピー) | 変わらない |
2次元のint配列なら、行ごとのclone()で深いコピーになります。 3次元以上や、要素が変更可能なオブジェクト(StringBuilder[]や自作クラスの配列など)の場合は、clone()では要素のオブジェクト自体は共有されるため、各要素もコピーする処理が必要です。要素をrecordやStringのような変更できない型にしておくと、共有されても問題が起きにくくなります。
2次元配列の表示はArrays.deepToString、比較はArrays.deepEqualsを使います。 Arrays.toStringやArrays.equalsでは、中の行が「同じ配列か」で比べられるため、中身が同じでもfalseになります(上のequals=false)。
Arraysクラスの便利メソッド一覧
java.util.Arraysには、配列の表示・比較・検索・変換のメソッドがまとまっています。 よく使うものを動かしました。
import java.util.Arrays;
import java.util.List;
public class ArraysUtil {
public static void main(String[] args) {
int[] a = {3, 1, 4, 1, 5};
int[] b = {3, 1, 4, 1, 5};
System.out.println("toString : " + Arrays.toString(a));
System.out.println("a == b : " + (a == b));
System.out.println("a.equals(b) : " + a.equals(b));
System.out.println("Arrays.equals : " + Arrays.equals(a, b));
System.out.println("Arrays.compare : " + Arrays.compare(new int[]{1, 2}, new int[]{1, 3}));
System.out.println("Arrays.mismatch : " + Arrays.mismatch(a, new int[]{3, 1, 9, 1, 5}));
System.out.println("hashCode 一致 : " + (Arrays.hashCode(a) == Arrays.hashCode(b)));
int[] sorted = {1, 3, 5, 7, 9};
System.out.println("binarySearch(7) : " + Arrays.binarySearch(sorted, 7));
System.out.println("binarySearch(4) : " + Arrays.binarySearch(sorted, 4) + "(負なら無い。-(挿入位置)-1)");
System.out.println("未ソートで検索 : " + Arrays.binarySearch(new int[]{9, 1, 5, 3}, 3) + "(結果は不定)");
System.out.println("stream 合計 : " + Arrays.stream(a).sum());
System.out.println("stream 最大 : " + Arrays.stream(a).max().getAsInt());
System.out.println("stream 平均 : " + Arrays.stream(a).average().getAsDouble());
System.out.println("stream 重複除去 : " + Arrays.toString(Arrays.stream(a).distinct().toArray()));
String[] names = {"佐藤", "鈴木", "高橋"};
List<String> fixed = Arrays.asList(names);
fixed.set(0, "田中");
System.out.println("asList.set 後の配列: " + Arrays.toString(names));
try {
fixed.add("伊藤");
} catch (UnsupportedOperationException e) {
System.out.println("asList.add : " + e);
}
List<int[]> wrong = Arrays.asList(a);
System.out.println("asList(int[]) の要素数: " + wrong.size());
List<String> list = List.of(names);
System.out.println("List.of(配列) : " + list);
String[] back = list.toArray(String[]::new);
System.out.println("List→配列 : " + Arrays.toString(back));
}
}
実行結果(Java 21.0.10):
toString : [3, 1, 4, 1, 5]
a == b : false
a.equals(b) : false
Arrays.equals : true
Arrays.compare : -1
Arrays.mismatch : 2
hashCode 一致 : true
binarySearch(7) : 3
binarySearch(4) : -3(負なら無い。-(挿入位置)-1)
未ソートで検索 : -3(結果は不定)
stream 合計 : 14
stream 最大 : 5
stream 平均 : 2.8
stream 重複除去 : [3, 1, 4, 5]
asList.set 後の配列: [田中, 鈴木, 高橋]
asList.add : java.lang.UnsupportedOperationException
asList(int[]) の要素数: 1
List.of(配列) : [田中, 鈴木, 高橋]
List→配列 : [田中, 鈴木, 高橋]
| メソッド | できること | 実行結果・注意 |
|---|---|---|
Arrays.toString(a) |
1次元配列を文字列に | [3, 1, 4, 1, 5] |
Arrays.equals(a, b) |
中身が同じか | true。a.equals(b)や==は「同じ配列か」なのでfalse |
Arrays.deepEquals(g, h) |
多次元の中身比較 | 2次元以上はこちら |
Arrays.compare(a, b) |
辞書順で大小比較(Java 9) | -1(aが小さい) |
Arrays.mismatch(a, b) |
最初に違う位置(Java 9) | 2。同じなら-1 |
Arrays.binarySearch(a, key) |
二分探索 | ソート済みが前提。無いときは-(挿入位置)-1 |
Arrays.stream(a) |
Streamに変換 | sum・max・average・distinctなど |
Arrays.asList(a) |
Listとして見る |
長さ固定でadd不可、setは元の配列に反映 |
List.of(a)/list.toArray(String[]::new) |
配列とListの変換 | List.ofは変更不可のList |
Arrays.asList(int[])は要素数1のList<int[]>になります(配列そのものが1要素として扱われるため)。int[]をListにしたいときはArrays.stream(a).boxed().toList()を使います。
binarySearchはソート済みの配列が前提です。 未ソートの配列に使うと、上の例のように実在する3を探しても負の値が返ることがあります(結果は不定)。
ArrayIndexOutOfBoundsExceptionの原因と直し方
ArrayIndexOutOfBoundsExceptionは、存在しない番号(インデックス)で配列にアクセスしたときに起きる例外です。 配列の番号は0からlength - 1までです。よくある4パターンを再現しました。
public class Aioobe {
public static void main(String[] args) {
int[] a = {10, 20, 30};
try {
for (int i = 0; i <= a.length; i++) {
System.out.println(a[i]);
}
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println(e);
}
try {
System.out.println(a[-1]);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println(e);
}
try {
int[] empty = new int[0];
System.out.println(empty[0]);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println(e);
}
try {
int[] neg = new int[-1];
} catch (NegativeArraySizeException e) {
System.out.println(e);
}
}
}
実行結果(Java 21.0.10):
10
20
30
java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
java.lang.ArrayIndexOutOfBoundsException: Index -1 out of bounds for length 3
java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0
java.lang.NegativeArraySizeException: -1
tryで囲まずにa[3]を読む1行だけのプログラム(Aioobe2.java)を実行すると、スタックトレースに行番号が表示されます。
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
at Aioobe2.main(Aioobe2.java:1)
メッセージのIndex 3 out of bounds for length 3は「長さ3の配列に番号3でアクセスした」という意味です。 番号とlengthが両方出るので、原因の当たりを付けやすくなっています。
| パターン | 原因 | 直し方 |
|---|---|---|
i <= a.length |
最後の番号はlength - 1 |
i < a.lengthにする。番号が不要なら拡張for for (int v : a) |
a[-1]・a[i - 1]でiが0 |
負の番号 | 先頭の扱いを分ける、i > 0を確認 |
空配列の[0] |
長さ0 | if (a.length > 0)で確認 |
System.arraycopyの範囲外 |
コピー先が短い | 長さを確認、またはArrays.copyOfで新しく作る |
new int[-1] |
長さが負 | NegativeArraySizeException。長さの計算を見直す |
配列とArrayListの使い分けとJava 25の状況
長さが決まっていて数値をたくさん扱うなら配列、件数が増減するならArrayListが向いています。 ArrayListならlist.sort(Comparator.reverseOrder())で逆順にでき、printlnでそのまま中身も表示できます。
Java 25について:手元の環境はJava 21.0.10のため、Java 22〜25では実行していません。公式のAPI差分一覧(Java SE 25の「New API」)を確認したところ、java.util.Arraysに追加されたメソッドはありませんでした。この記事の書き方はJava 25でもそのまま使えます。
よくある質問
Q. 配列の初期化で、{1, 2, 3}とnew int[]{1, 2, 3}はどう違いますか?
A. 作られる配列は同じです。 違いは書ける場所で、{1, 2, 3}は宣言と同時(int[] a = {1, 2, 3};)のときだけ、new int[]{1, 2, 3}は代入・引数・returnなど、どこでも書けます。
Q. 配列のコピーはcloneとArrays.copyOfのどちらを使えばよいですか?
A. 同じ長さでまるごとコピーするならclone()、長さを変えたいならArrays.copyOfです。 既にある配列に詰めたいときだけSystem.arraycopyを使います。2次元配列はどちらも浅いコピーになるので、行ごとにコピーしてください。
Q. int[]を降順に並べ替えるにはどうすればよいですか?
A. int[]にはCollections.reverseOrder()を渡せないので、Integer[]にする、IntStreamでboxed().sorted(Comparator.reverseOrder())する、昇順にしてから前後を入れ替える、のいずれかです。 件数が多いときは入れ替え方式が向いています。
Q. Arrays.sortは安定ソートですか?
A. オブジェクトの配列(String[]やrecordの配列など)は安定ソートで、同じ値の要素の順番は保たれます。 int[]などの基本型は値そのものしか持たないので、順番の区別は問題になりません。
まとめ
- 初期化は5パターン:
new int[5](既定値で埋まる)、{...}(宣言時のみ)、new int[]{...}、Arrays.fill、IntStream/Arrays.setAll int[] b = a;はコピーではない。コピーはclone()・Arrays.copyOf・copyOfRange・System.arraycopyArrays.sortで昇順。逆順はInteger[]+Collections.reverseOrder()、int[]のままならStreamか反転int[]にComparatorを渡すとコンパイルエラー(Comparatorは基本型を扱えない)- 2次元配列の
clone()は浅いコピー。行ごとにclone()するかArrays.stream(g).map(int[]::clone)で深いコピーに - 表示・比較は
toString/equals、2次元以上はdeepToString/deepEquals ArrayIndexOutOfBoundsExceptionは番号の範囲(0〜length-1)を確認。全コードはJava 21.0.10で実行して確認
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java配列の初期化・コピー・Arrays.sort|逆順と浅いコピーの注意点」
株式会社RJCでは、配列のような基礎の動きを一つずつ確かめながら、仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。