フロー制御の全体像
フロー制御は「どの順番で、どの条件で、どこまで処理するか」を設計することです。Javaでは if/switch、for/while、break/continue、early return、try-catch-finally などを組み合わせて、意図どおりに流れを作ります。大事なのは「読み手に分かる順序」「失敗時の安全な着地」「不要な処理の回避」。構文の使い方だけでなく、どの地点で判断し、どこで止め、どこで後始末するかを設計として決めます。
設計の指針(読みやすさ・安全性・効率)
読みやすさを最優先にする
- 直線化: 否定条件は入口で弾き、主要ロジックは一本の流れにまとめる(early return)。
- 役割分離: 入力検証、メイン処理、後始末を物理的に分ける(メソッド分割やブロック化)。
- 名前で意図を示す: 複雑な条件は一度真偽値に落として「質問文の変数」で判定する。
boolean hasText = s != null && !s.isBlank();
boolean isAdult = age >= 18;
if (hasText && isAdult) submit();
Java安全に落ちる道を常に用意する
- ガード節: 前提違反は即座に例外または早期リターンで止める。
- 後始末の保証: finally や try-with-resources でリソースを必ず解放する。
- 失敗の意味づけ: 例外メッセージや型で原因と文脈を伝える。
効率を設計に取り込む
- 早期終了: 条件不成立ならすぐ抜けて、無駄な計算やI/Oを避ける。
- 前処理の活用: ループ内の重い計算や検索は、セット化・マップ化・事前ソートで削減する。
- 短絡評価: 安い判定を左、重い判定を右に置き、不要な評価を避ける。
分岐設計:if と switch を使い分ける
if の基本とネスト削減
- 肯定形で並べる: “何を満たせば進むか”を上から順に。
- 早期リターン: else を減らし、主要ロジックを最後に残す。
void process(User u) {
if (u == null) return;
if (!u.isActive()) return;
if (u.isBanned()) return;
sendMail(u); // 本筋
}
Javaswitch と enum で平らに表現する
- 列挙で型安全: 文字列や数字の分岐より漏れが少ない。
- 表形式の読みやすさ: ケースごとの処理が一望できる。
enum Status { NEW, PROCESSING, DONE, FAILED }
String label(Status s) {
return switch (s) {
case NEW -> "新規";
case PROCESSING -> "処理中";
case DONE -> "完了";
case FAILED -> "失敗";
};
}
Javaループ設計:役割・境界・終了戦略
境界条件を明示し、インデックスを手放す
- 原則: 0 ≤ index < size、end は排他(substring の [begin, end))。
- foreach: インデックス不要なら foreach で安全・簡潔に。
for (String name : names) {
if (name == null || name.isBlank()) continue; // 早期スキップ
System.out.println(name.trim());
}
Java早期終了で無駄を削る(ラベル付き break)
- 目的達成で即終了: ネストした探索は、見つかった瞬間に外側ごと抜ける。
search:
for (int r = 0; r < grid.length; r++) {
for (int c = 0; c < grid[r].length; c++) {
if (grid[r][c] == 42) {
System.out.println("found at " + r + "," + c);
break search; // 外側まで抜ける
}
}
}
Java前処理でループを軽くする
- セット化: 二重ループの照合は Set.contains へ置き換える。
- 事前ソート+二本指法: O(n·m) を O(n+m) に近づける。
var setB = new java.util.HashSet<>(listB);
for (String a : listA) {
if (setB.contains(a)) link(a);
}
Java失敗と後始末:try-catch-finally とリソース管理
失敗は「想定外のみ」例外で扱う
- 契約違反: IllegalArgumentException/IllegalStateException を即座に throw。
- 外部要因: IOException/SQLException はこの層で回復しないなら throws で上位へ。
int taxed(int subtotal, double rate) {
if (subtotal < 0) throw new IllegalArgumentException("subtotal must be >= 0");
if (rate < 0 || rate > 1) throw new IllegalArgumentException("rate must be 0..1");
return (int) Math.round(subtotal * (1 + rate));
}
Java後始末は必ず保証する
- try-with-resources: AutoCloseable を自動クローズ。早期リターンでも安全。
import java.nio.file.*;
import java.io.*;
String firstLineOrEmpty(Path p) throws IOException {
try (var br = Files.newBufferedReader(p)) {
String line = br.readLine();
if (line == null || line.isBlank()) return "";
return line.trim();
} // ここで自動クローズ
}
Java- finally: 設定やロックの復元は finally で確実に。
var lock = new java.util.concurrent.locks.ReentrantLock();
lock.lock();
try {
if (!ready()) return; // 早期終了
work();
} finally {
lock.unlock(); // 必ず解放
}
Javaネストを構造で消すテクニック
メソッド分割で一責務化
- 小さい関数: 検証、分岐応答、メイン処理を関数に分けると読みやすい。
void handle(Request r) {
if (isAuthFailed(r)) { respondUnauthorized(); return; }
if (isRateLimited(r)) { respondTooManyRequests(); return; }
processOk(r);
}
Javaデータ駆動(表で分岐を置き換える)
- マップ+関数: 条件→処理 をテーブル化すると追加が容易でネストが消える。
import java.util.Map;
import java.util.function.Function;
Map<String, Function<Order, Integer>> pricing = Map.of(
"JP", o -> o.qty() * 100,
"US", o -> o.qty() * 100,
"EU", o -> o.qty() * 90
);
int price(String region, Order o) {
var f = pricing.get(region);
if (f == null) throw new IllegalArgumentException("unsupported region: " + region);
return f.apply(o);
}
Java状態機械で流れを可視化する
- 状態+遷移: 多段の分岐や工程は「状態」と「イベント」で管理すると、順序が明確になります。
enum State { INIT, PROCESSING, DONE, FAILED }
State next(State s, boolean ok) {
return switch (s) {
case INIT -> ok ? State.PROCESSING : State.FAILED;
case PROCESSING -> ok ? State.DONE : State.FAILED;
default -> s;
};
}
Java例題で身につける
例 1: 早期リターンで分岐を直線化
int fee(String plan, int qty) {
if (qty <= 0) return 0; // ガード
if (plan == null) return 100 * qty; // 既定
if (plan.equals("PRO")) return 90 * qty;
return 100 * qty;
}
Java例 2: ネスト探索の早期終了と前処理
int[][] g = {{1,2,3},{4,42,6}};
found:
for (int r = 0; r < g.length; r++) {
for (int c = 0; c < g[r].length; c++) {
if (g[r][c] == 42) { System.out.println(r + "," + c); break found; }
}
}
Java例 3: リソース管理とエラー伝播の基本形
import java.nio.file.*;
import java.io.*;
String read(Path p) throws IOException {
try (var br = Files.newBufferedReader(p)) {
String line = br.readLine();
if (line == null) return "";
return line.trim();
} // 後始末は自動、失敗は IOException で上位へ
}
Java仕上げのアドバイス(重要部分のまとめ)
- 直線化: 否定条件を入口で弾き、主要ロジックは一本に。else を減らす。
- 終了戦略: 早期終了で無駄を避け、ラベル付き break や continue でネストを浅く。
- 後始末: try-with-resources と finally を徹底し、失敗時でも安全に着地。
- 置き換え: Set/Map、switch/enum、状態機械、メソッド分割で「構造化」してネストを消す。
- 契約: 例外はバグや契約違反、早期リターンは想定内の不成立。意図をコードに刻む。
