配列を for で走査

生田 陸人
LuaGate エンジニア / 現役エンジニア
編集 LuaGate編集部

このレッスンで分かること

  • 配列走査の定番は 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 — インデックス i0 から始める (配列の添字は 0 始まり)
  • i < arr.lengthiarr.length 未満 の間ループを続ける
  • i++ — 1 周ごとに i1 増やす

ここで一番大事なのは、継続条件が 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 で偶数番目と奇数番目を区別したりできる
  • 複数の配列を同時に回せる — 同じ長さの arr1arr2arr1[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 (組み立て中の文字列) がどう変わっていくかを図にしてみました。

diagram (will load when visible)

各ループでの値の変化を表にすると、次のとおりです。

iarr[i]i > 0追加処理sb
01falsesb.append(1)"1"
12truesb.append(", ")sb.append(2)"1, 2"
23truesb.append(", ")sb.append(3)"1, 2, 3"
3(条件外)ループ終了"1, 2, 3" を return

見てわかるとおり、result の中身は 1 周ごとに「カンマ + 空白 + 次の要素」が末尾に追加されていきます。i = 0 のときだけは先頭なのでカンマを付けず、i >= 1 のときは前に ", " を入れる、というのが「末尾にカンマを残さない」コツです。

実装の方針

方針を整理すると、次の 3 つのステップになります。

  1. 結果を貯めるバッファ (StringBuilder または String) を空で用意する
  2. for (int i = 0; i < arr.length; i++)i0 から arr.length - 1 まで動かす
  3. 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.length0 なら i < 0 が最初から false なので、ループは 1 周も回らず、sb は空のまま "" が返ります。「ループに入らないケース」も同じコードで通せるのが、for (int i = 0; i < arr.length; ...) パターンの気持ちよさです。

別解 ─ IntStreamreduce を使う

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.lengthi <= 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.length0 の場合に何を返すかを考えていないと、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 周回してみる癖をつけるとデバッグが速くなります。

forString.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) メソッドを完成させてみましょう。手順は次のとおりです。

  1. 結果用に StringBuilder sb = new StringBuilder(); を用意する
  2. for (int i = 0; i < arr.length; i++) のループを書く
  3. if (i > 0) { sb.append(", "); } で 2 つ目以降に区切り文字を付ける
  4. ループの中で sb.append(arr[i]); を呼ぶ
  5. ループの後ろで 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 つができることを確認しましょう。

  1. 配列走査 の要点を自分の言葉で説明できる
  2. このレッスンの最小コード (または操作手順) を見ずに書ける
  3. 練習問題やクイズで間違えた箇所を読み直して理解した

理解度チェック (30 秒)

Q. 配列走査 とは何か、1 文で説明してください。

この章のポイント

A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。

関連レッスン

要件

  1. クラス名は Solution、メソッド名は joinElements、引数は int[] arr 1 つ、戻り値の型は String
  2. for (int i = 0; i < arr.length; i++) パターンで配列を走査すること (継続条件は <<= にしないこと)
  3. 区切り文字は , (カンマ + 半角スペース 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"

ヒント

main.java
main.java
学習モード

メモ

配列を for で走査

⌘S で保存