Java:配列
1学習の目的
- 配列の宣言・初期化・操作ができるようになる。変数を100個作る代わりに、1つの配列で管理できるようになる。
- 配列は「箱そのもの」ではなく「箱の場所」を持つという参照の仕組みを理解する。これを知らないと、意図せずデータを壊してしまう。
2基礎解説
| やりたいこと | 書き方 | 備考 |
|---|---|---|
| 値を並べて作る | int[] a = {10, 20, 30}; | 最も簡潔 |
| 大きさだけ決める | int[] b = new int[3]; | 初期値が入る |
| 要素数を知る | a.length | カッコは付けない |
| 中身を表示 | Arrays.toString(a) | importが必要 |
- 添字は0から始まる。要素が3つなら添字は0・1・2で、最後の要素は a[a.length - 1]。a[3] と書くと範囲外エラーになる。
- new int[3] は自動的に初期値が入る。数値なら0、booleanなら false、Stringなど参照型なら null。null に対して操作するとエラーになるので注意。
- ⭐ 配列を代入しても中身はコピーされない。int[] b = a; と書くと同じ配列を2つの名前で指すだけ。片方を変えるともう片方も変わる。
- 中身をコピーしたいなら Arrays.copyOf を使う。これなら独立した別の配列になり、一方を変えても他方に影響しない。
- 配列の長さは後から変えられない。要素を増やしたいなら作り直すか、可変長のList(JV12)を使う。
現場使用例:商品一覧、点数の集計、月別データ、CSVの1行分。「同じ種類のデータが複数ある」場面はすべて配列の出番。
import java.util.Arrays; // 配列の便利メソッドを使うため
// 宣言のしかた
int[] a = {10, 20, 30}; // 値を並べる
int[] b = new int[3]; // 大きさだけ決める(0が3つ)
String[] s = new String[2]; // null が2つ
// 添字は0から
System.out.println(a[0]); // 10
System.out.println(a[a.length - 1]); // 30(最後)
// System.out.println(a[3]); // ⚠ 範囲外エラー
// 値の変更
a[1] = 99;
// 全要素を処理
for (int v : a) { … } // 添字が不要なとき
for (int i = 0; i < a.length; i++) { … } // 添字が必要なとき
// ⭐ 代入は「中身のコピー」ではない
int[] x = {1, 2, 3};
int[] y = x; // 同じ配列を指すだけ
y[0] = 999;
System.out.println(x[0]); // 999(元も変わる!)
// 中身をコピーしたいとき
int[] z = Arrays.copyOf(x, x.length); // 独立した別の配列
// 便利なメソッド
Arrays.toString(a); // "[10, 99, 30]" 表示用
Arrays.sort(a); // 小さい順に並べ替え(元を変更)
Arrays.fill(b, 7); // 全要素を7にする
Arrays.equals(a, b); // 中身が同じか
// 2次元配列(表形式)
int[][] grid = {{1, 2, 3},
{4, 5, 6}};
System.out.println(grid[1][2]); // 6
System.out.println(grid.length); // 2(行数)
System.out.println(grid[0].length);// 3(列数)
3基本ドリル(10問)
出力2行。10 / 30
最後は a[a.length - 1] で取り出す。
public class Main {
public static void main(String[] args) {
int[] a = {10, 20, 30};
System.out.println(a[0]);
System.out.println(a[a.length - 1]);
}
}添字は0から始まるので、最後は length - 1。a[a.length] と書くと範囲外エラーになる。
A. null B. 0 C. 未定義 D. エラー 選択★☆☆無料
解答 == B
数値型の初期値は何か。
B
数値なら0、booleanなら false、String など参照型なら nullが自動的に入る。使う前に必ず値が入っている点はJavaの安全な設計。
出力2行。0 / null
new int[3] と new String[3] を作り、[0] を出力する。
public class Main {
public static void main(String[] args) {
int[] nums = new int[3];
String[] names = new String[3];
System.out.println(nums[0]);
System.out.println(names[0]);
}
}String配列の初期値は null。そのまま names[0].length() を呼ぶと NullPointerException になるので、必ず値を入れてから使う。
public class Main {
public static void main(String[] args) {
int[] a = {10, 20, 30};
System.out.println(a.____);
}
}出力 == 3
「長さ」を意味する6文字。カッコは付けない。
length
配列は length(カッコなし)、文字列は length()(カッコあり)——紛らわしいが区別する必要がある。
A. 元のまま B. 999になる C. エラー D. 0になる 選択★☆☆無料
解答 == B
配列の代入は中身のコピーではない。
B
同じ配列を2つの名前で指しているだけ。片方を変えればもう片方も変わる——意図せずデータを壊す原因になる。
出力 == [10, 20, 30]
import java.util.Arrays; が必要。
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] nums = {30, 10, 20};
Arrays.sort(nums);
System.out.println(Arrays.toString(nums));
}
}Arrays.sort は元の配列を並べ替える(新しい配列を返すのではない)。元の順序を残したいなら、先にコピーを取る。
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] a = {1, 2, 3};
int[] b = Arrays.____(a, a.length);
b[0] = 999;
System.out.println(a[0]);
}
}出力 == 1
「〜のコピー」を意味する6文字のメソッド。
copyOf
copyOf なら独立した配列になるので、コピー側を変えても元は 1 のまま。単純な代入との違いがはっきり分かる。
A. null が返る B. 実行時エラー C. コンパイルエラー D. 0が返る 選択★★☆広告解放
解答 == B
添字は0・1・2まで。
B
ArrayIndexOutOfBoundsException で落ちる。コンパイルは通ってしまうので、実行するまで気づけない——ループの条件を <= と書くミスが典型例。
出力3行。6 / 2 / 3
grid[1][2] が2行目3列目。行数は grid.length、列数は grid[0].length。
public class Main {
public static void main(String[] args) {
int[][] grid = {{1, 2, 3}, {4, 5, 6}};
System.out.println(grid[1][2]);
System.out.println(grid.length);
System.out.println(grid[0].length);
}
}2次元配列は「配列の配列」。grid[1] が2行目の配列を指し、そこからさらに [2] で3列目を取り出す。
出力 == [7, 7, 7]
Arrays.fill(配列, 値) で一括設定できる。
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] a = new int[3];
Arrays.fill(a, 7);
System.out.println(Arrays.toString(a));
}
}初期値を一括で設定したいときに便利。ループで1つずつ入れるより簡潔に書ける。
4実践シナリオ(5問)
出力3行。314 / 78.5 / 91
平均は小数にキャストする。String.format("%.1f", 値) で桁を揃える。
public class Main {
public static void main(String[] args) {
int[] scores = {80, 75, 91, 68};
int sum = 0;
int max = scores[0];
for (int s : scores) {
sum += s;
if (s > max) {
max = s;
}
}
System.out.println(sum);
System.out.println(String.format("%.1f", sum / (double) scores.length));
System.out.println(max);
}
}1周で合計と最大値の両方を求めている。データを何度も読み直す必要はない。
出力2行。999 / 1
代入した配列を変更すると元も変わる。copyOf なら変わらない。
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] a = {1, 2, 3};
int[] ref = a;
ref[0] = 999;
System.out.println(a[0]);
int[] b = {1, 2, 3};
int[] copy = Arrays.copyOf(b, b.length);
copy[0] = 999;
System.out.println(b[0]);
}
}同じ操作でも結果がまったく違う。代入は「別名を付けただけ」、copyOf は「複製を作る」——この違いを知らないと、原因不明のデータ破壊に悩まされる。
出力3行
添字を使う通常のforで、names[i] と prices[i] を同時に使う。
public class Main {
public static void main(String[] args) {
String[] names = {"ノートPC", "マウス", "モニター"};
int[] prices = {128000, 3200, 45000};
for (int i = 0; i < names.length; i++) {
System.out.println(names[i] + ":"
+ String.format("%,d", prices[i]) + "円");
}
}
}2つの配列を添字で対応させるのは古典的な手法。ただし配列の長さがずれると破綻するので、JV08で学ぶクラスのほうが安全になる。
出力2行。128000 / 45000
Arrays.sort で小さい順にしてから、後ろから2つ取り出す。
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] prices = {45000, 128000, 3200, 8500};
Arrays.sort(prices);
for (int i = prices.length - 1; i >= prices.length - 2; i--) {
System.out.println(prices[i]);
}
}
}Arrays.sort は小さい順にしかできないので、大きい順に取り出したいなら後ろからループする。逆順のソートには別の方法が必要(JV12で扱う)。
出力3行。150 / 175 / 135
外側のループで人、内側のループで科目を回す。
public class Main {
public static void main(String[] args) {
int[][] scores = {{80, 70}, {90, 85}, {60, 75}};
for (int i = 0; i < scores.length; i++) {
int sum = 0;
for (int j = 0; j < scores[i].length; j++) {
sum += scores[i][j];
}
System.out.println((i + 1) + "人目:" + sum + "点");
}
}
}合計用の変数を内側のループの外・外側のループの中で宣言するのが要点。外に出すと全員分が合算され、内に入れると毎回0に戻る。
5仕上げ課題
データ
String[] names = {"ノートPC", "マウス", "モニター", "キーボード"};
int[] prices = {128000, 3200, 45000, 8500};
int[] stocks = {5, 42, 0, 18};
処理
① 各商品を「1. ノートPC:128,000円 × 5個 = 640,000円」の形式で出力
・在庫0の商品は末尾に ※品切れ を付ける
② 在庫総額(単価×在庫の合計)を計算
③ 在庫がある商品だけの平均在庫数を小数第1位まで表示
④ 在庫金額が最も高い商品名を出力
期待される出力(7行)
1. ノートPC:128,000円 × 5個 = 640,000円
2. マウス:3,200円 × 42個 = 134,400円
3. モニター:45,000円 × 0個 = 0円 ※品切れ
4. キーボード:8,500円 × 18個 = 153,000円
---
在庫総額:927,400円(最高:ノートPC)
平均在庫数:21.7個(在庫あり3件) 総合★★★広告解放
出力7行が完全一致
品切れ判定は stocks[i] == 0。平均在庫は在庫0を除いて計算する(5+42+18=65、65÷3=21.666…)。金額は String.format("%,d", 値)、平均は String.format("%.1f", 値) で整形。
public class Main {
public static void main(String[] args) {
String[] names = {"ノートPC", "マウス", "モニター", "キーボード"};
int[] prices = {128000, 3200, 45000, 8500};
int[] stocks = {5, 42, 0, 18};
int totalValue = 0;
int maxValue = 0;
String maxName = "";
int stockSum = 0;
int stockCount = 0;
for (int i = 0; i < names.length; i++) {
int value = prices[i] * stocks[i];
totalValue += value;
if (value > maxValue) {
maxValue = value;
maxName = names[i];
}
if (stocks[i] > 0) {
stockSum += stocks[i];
stockCount++;
}
String mark = (stocks[i] == 0) ? " ※品切れ" : "";
System.out.println((i + 1) + ". " + names[i] + ":"
+ String.format("%,d", prices[i]) + "円 × "
+ stocks[i] + "個 = "
+ String.format("%,d", value) + "円" + mark);
}
double avgStock = stockSum / (double) stockCount;
System.out.println("---");
System.out.println("在庫総額:" + String.format("%,d", totalValue)
+ "円(最高:" + maxName + ")");
System.out.println("平均在庫数:" + String.format("%.1f", avgStock)
+ "個(在庫あり" + stockCount + "件)");
}
}3つの配列を添字で対応させ、1周で5つの集計を同時に行っている。在庫金額、総額への加算、最大値の更新、在庫数の合計、在庫あり件数——データを1度読むだけで、必要な情報をすべて取り出せる。
注目してほしいのは「在庫があるものだけ」の平均だ。単純に 全在庫 ÷ 4 とすると16.25個になるが、品切れの商品を含めた平均に意味はあるだろうか。集計の対象をどう定義するかは、プログラムの問題ではなく業務の問題だ。仕様を決める人と認識を合わせておかないと、正しく動いているのに「求めていた数字と違う」と言われることになる。
そしてこの課題の弱点も見ておきたい。3つの配列が添字で結びついているだけなので、もし誰かが names に1件追加して prices を忘れれば、実行時に範囲外エラーで落ちる。「ノートPCの価格は128000円」という関係が、コード上のどこにも表現されていないのだ。
この危うさを解決するのが、JV08で学ぶクラスだ。商品を1つのオブジェクトとしてまとめれば、名前・価格・在庫が常に一体で動くようになり、ずれようがなくなる。
次章ではメソッドを学ぶ。長くなってきた main の中身を、役割ごとに切り分ける方法を身につける。