配列を for で走査
このレッスンで分かること
- 配列走査の定番は
for (int i = 0; i < arr.length; i++)、<=にすると例外- 区切り文字を末尾に残さないコツは「先頭以外は前に区切りを足す」(
if (i > 0))- 最小例は
for (int i = 0; i < arr.length; i++) { if (i > 0) sb.append(", "); sb.append(arr[i]); }
配列を for で走査 とは
配列の全要素を for ループで走査し、
,(カンマ + 空白) で連結した文字列を組み立てる練習をします。
配列を端から端まで歩く
配列を作って中身を入れただけでは、まだ半分しか使いこなしていません。本当に役に立つのは、for ループで配列の中身を 端から端まで順番に取り出して何かする ようになってからです。合計を出す、最大値を探す、画面に表示する、CSV の 1 行に組み立てる ─ 現場で書く Java コードの大半は、突き詰めると「配列 (やリスト) をループでなめる」だけだと言ってもいいくらいです。
このレッスンでは、int[] arr を受け取って "1, 2, 3" のような カンマ + 半角スペース区切りの文字列 に組み立てるメソッド joinElements を書きます。一見地味なお題ですが、for で配列を走査する基本パターンが全部詰まっています。一度しっかり手に馴染ませると、ここから先の JavaScript でも Python でも Go でも、同じ発想がそのまま通用します。
配列の走査は、英語で
traversal(トラバーサル) と呼びます。「配列の頭から尻尾まで一度ずつ訪ねる」というニュアンスです。これから書くコードのうち、体感で 8 割くらいは何らかの形でトラバーサルが入っています。一番大事な型のひとつだと思って向き合いましょう。
基本パターン ─ for (int i = 0; i < arr.length; i++)
配列を for でなめるときの定番の書き方は、次の形です。
Java
for (int i = 0; i < arr.length; i++) {
int value = arr[i];
// value を使った処理
}カッコの中の 3 つのパーツの意味を整理すると、次のとおりです。
int i = 0— インデックスiを0から始める (配列の添字は0始まり)i < arr.length—iがarr.length未満 の間ループを続けるi++— 1 周ごとにiを1増やす
ここで一番大事なのは、継続条件が i < arr.length だという点です。配列の長さが 3 なら添字は 0, 1, 2 の 3 つだけで、arr[3] は存在しません。i <= arr.length と書いてしまうと、最後の周で存在しない arr[3] にアクセスしようとして ArrayIndexOutOfBoundsException という例外で落ちます。< と <= の違いだけでクラッシュするので、まず最初に体で覚えるルールです。
Java の
forで配列をなめるときは、合言葉のように「i < arr.length、イコールは付けない」と唱えてください。本当によくやる失敗で、現役エンジニアでも疲れているときにi <= lengthと書いてしまい、テストでバグを見つけることがあります。
インデックスを使うメリット
「配列を順番になめるだけなら拡張 for 文 (後で紹介します) でいいのでは?」と思うかもしれません。実は i が手元にあること自体に、いくつかのメリットがあります。
- 位置情報が使える — 「何番目の要素か」が
iでわかるので、0番目だけ特別扱いしたり、i % 2で偶数番目と奇数番目を区別したりできる - 複数の配列を同時に回せる — 同じ長さの
arr1とarr2をarr1[i]とarr2[i]でペアで取り出せる - 末尾の判定がしやすい —
i == arr.length - 1で「最後の要素か?」を判定でき、区切り文字の制御に使える - 逆順走査もできる —
for (int i = arr.length - 1; i >= 0; i--)のように、添字を逆に動かせば末尾から走査できる
今回の課題でも、"1, 2, 3" の末尾にカンマと空白を残さないために、「最初の要素は区切り文字なし、2 つ目以降は前に ", " を付ける」というパターンを使います。これは i == 0 かどうかを判定して分岐すれば自然に書けます。
Mermaid 図で走査の流れを追う
arr = {1, 2, 3} を渡したとき、ループの中で result (組み立て中の文字列) がどう変わっていくかを図にしてみました。
各ループでの値の変化を表にすると、次のとおりです。
i | arr[i] | i > 0 | 追加処理 | sb |
|---|---|---|---|---|
| 0 | 1 | false | sb.append(1) | "1" |
| 1 | 2 | true | sb.append(", ") → sb.append(2) | "1, 2" |
| 2 | 3 | true | sb.append(", ") → sb.append(3) | "1, 2, 3" |
| 3 | — | (条件外) | ループ終了 | "1, 2, 3" を return |
見てわかるとおり、result の中身は 1 周ごとに「カンマ + 空白 + 次の要素」が末尾に追加されていきます。i = 0 のときだけは先頭なのでカンマを付けず、i >= 1 のときは前に ", " を入れる、というのが「末尾にカンマを残さない」コツです。
実装の方針
方針を整理すると、次の 3 つのステップになります。
- 結果を貯めるバッファ (
StringBuilderまたはString) を空で用意する for (int i = 0; i < arr.length; i++)でiを0からarr.length - 1まで動かすi == 0ならarr[i]をそのまま追加、i >= 1なら", "を先に追加してからarr[i]を追加する
コードにするとこうなります。
Java
public class Solution {
public static String joinElements(int[] arr) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < arr.length; i++) {
if (i > 0) {
sb.append(", ");
}
sb.append(arr[i]);
}
return sb.toString();
}
}StringBuilder を使っている理由は、前のレッスンと同じく String の + 連結はループ回数が増えると遅くなる からです。sb.append(...) で末尾に書き足し、最後に sb.toString() で String に変換します。
空配列のケースも、このコードは自然に処理できます。arr.length が 0 なら i < 0 が最初から false なので、ループは 1 周も回らず、sb は空のまま "" が返ります。「ループに入らないケース」も同じコードで通せるのが、for (int i = 0; i < arr.length; ...) パターンの気持ちよさです。
別解 ─ IntStream と reduce を使う
Java 8 以降では、Stream API を使ってループ処理をメソッドチェーンで表現できます。IntStream.of(arr) で int[] を Stream に変換し、reduce で隣同士の文字列を ", " でつなぎながら畳み込むと、1 つの文字列が出来上がります。
Java
import java.util.stream.IntStream;
public class Solution {
public static String joinElements(int[] arr) {
return IntStream.of(arr)
.mapToObj(String::valueOf)
.reduce((a, b) -> a + ", " + b)
.orElse("");
}
}reduce は 2 つの要素を受け取って 1 つに縮める操作を繰り返す仕組みで、Stream が空の場合は Optional が empty になるため .orElse("") で空文字列を返します。IntStream を使うと int[] をそのまま Stream に変換できます。
for文版とStream版、どちらが正解ということはありません。読み手のレベルや、チームのコーディングスタイルで使い分けます。入門段階ではforで愚直に書けることが大事です。forで骨組みを掴んでから、Streamの世界に進むと「ああ、これは中でforを回しているんだな」と腑に落ちます。
慣れてきたら、まず for 版を写経して動かしてから、上の Stream 版に書き換えてもテストが通ることを試してみると、Java の表現の幅が一気に広がります。
よくある間違い
配列を for で走査するとき、ほぼ全員が一度はハマる落とし穴を 3 つ紹介します。
i < arr.lengthをi <= arr.lengthにしてしまう — 配列の添字は0からarr.length - 1までです。i <= arr.lengthだと最後の周でarr[arr.length]という存在しない要素にアクセスし、ArrayIndexOutOfBoundsExceptionで落ちます。継続条件は必ず<、と決めてしまうのが安全です- 末尾に余計な
,が残る —result = result + arr[i] + ", ";のように書くと、最後の要素の後ろにもカンマと空白が付いてしまい、"1, 2, 3, "のような出力になります。テストはこれを fail と判定します。先頭だけ特別扱い (if (i > 0)で前に", ") で組み立てるのが定番のテクニックです - 空配列の処理を忘れる —
arr.lengthが0の場合に何を返すかを考えていないと、NullPointerExceptionや思わぬ文字列を返してしまうことがあります。今回の課題では空配列は""を返す決まりです。for (int i = 0; i < arr.length; i++)パターンなら自然にループをスキップしてくれるので、安心して使えます
もうひとつ、配列そのものが null だった場合の挙動も実務では重要です。arr.length を呼んだ瞬間に NullPointerException で落ちるので、本来は if (arr == null) return ""; のようなガード節を入れるのが定石です。今回のテストでは null は渡しませんが、頭の片隅に入れておきましょう。
forループは便利な一方で、<と<=、0始まりと1始まり、空配列、null─ といった小さな落とし穴が点在しています。エラーが出たら「i はどこから始まってどこで止まるか」「arr.lengthはいくつか」「arr[i]は存在するか」を頭の中で 1 周回してみる癖をつけるとデバッグが速くなります。
for と String.join の使い分け
ここまでで for 文と String.join (Stream 経由) の 2 通りの書き方を見てきました。実務での使い分けの目安は、次のような感じです。
for文を選ぶ —iを計算に使いたい / 途中でbreakしたい / 条件付きで一部だけ追加したい /int[]を素直に扱いたいときStream+reduceを選ぶ —List<String>や配列をメソッドチェーンで加工しつつつなぎたいとき、あるいは関数型スタイルで統一したいとき
どちらも書けるようにしておくと、「for でちゃんと書けるけど、シンプルな場合は Stream で 1 行にする」という選択ができるようになります。プロのコードを読むとどちらも頻繁に出てくるので、両方の手札を持っておきましょう。
ここまでの要点
配列走査は for (int i = 0; i < arr.length; i++)、<= は例外を出す。区切り文字は「先頭以外は前に追加」(if (i > 0)) が定石。空配列は自然にループスキップで "" を返す。
やってみよう
それでは右側のエディタで、Solution.joinElements(int[] arr) メソッドを完成させてみましょう。手順は次のとおりです。
- 結果用に
StringBuilder sb = new StringBuilder();を用意する for (int i = 0; i < arr.length; i++)のループを書くif (i > 0) { sb.append(", "); }で 2 つ目以降に区切り文字を付ける- ループの中で
sb.append(arr[i]);を呼ぶ - ループの後ろで
return sb.toString();で完成した文字列を返す
動いたら、String.join バージョンに書き換えてみたり、int[] ではなく double[] を受け取る joinDoubles を作ってみたり、自分なりに発展させてみてください。同じ仕様を 2 通りの書き方で実装すると、それぞれの強みと弱みが体で理解できます。
配列を for でなめるのは、Java で書くあらゆるコードの土台になります。ここで身についた for (int i = 0; i < arr.length; i++) のリズムは、これから先のレッスンで何度も顔を出します。最初の 1 回は丁寧に、2 回目以降は無意識に書けるくらいまで、繰り返し練習しておきましょう。
よくある質問
Q. ビット演算は実務でいつ使いますか?
A. フラグ管理(permission の rwx)、画像処理(マスク)、ハッシュ計算の高速化などで使います。複数の真偽値を 1 つの整数にまとめると、メモリ削減と演算高速化の両方が得られます。例えば 8 個の権限を 1 byte で表せます。
Q. AND / OR / XOR の代表的な使い道は?
A. AND は「特定ビットの確認・抽出」(flags & PERM_READ)、OR は「ビットの立て上げ」(flags |= PERM_WRITE)、XOR は「ビット反転や暗号化」で使います。XOR は 2 回かけると元に戻る性質から、簡易暗号や両端ポインタの入れ替えなどに応用できます。
Q. シフト演算で割り算しても良いですか?
A. 正の整数の 2 のべき乗での除算なら x >> n と x / (1<<n) は同じ結果になります。ただし負の数では言語ごとに挙動が違う(算術シフトと論理シフトの区別)ため、可読性と安全性のために通常の演算子を使い、性能ボトルネックでのみシフトを検討してください。
次のレッスン
次は 配列の合計 で、配列の全要素を for ループで走査し、, (カンマ + 空白) で連結した文字列を組み立てる練習をします を学びます。
事前確認 — 進む前に次の 3 つができることを確認しましょう。
- 配列走査 の要点を自分の言葉で説明できる
- このレッスンの最小コード (または操作手順) を見ずに書ける
- 練習問題やクイズで間違えた箇所を読み直して理解した
理解度チェック (30 秒)
Q. 配列走査 とは何か、1 文で説明してください。
A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。
関連レッスン
要件
- クラス名は
Solution、メソッド名はjoinElements、引数はint[] arr1 つ、戻り値の型はString for (int i = 0; i < arr.length; i++)パターンで配列を走査すること (継続条件は<、<=にしないこと)- 区切り文字は
,(カンマ + 半角スペース 1 つ)。末尾に余計な,を残さないこと。空配列のときは""を返すこと
入出力例
test-cases.txt
joinElements([1,2,3]) → "1, 2, 3"
joinElements([]) → ""
joinElements([42]) → "42"
joinElements([10,20,30,40,50]) → "10, 20, 30, 40, 50"
joinElements([-1,0,7]) → "-1, 0, 7"