Java | 基礎文法:フロー制御の設計

Java Java
スポンサーリンク

フロー制御の全体像

フロー制御は「どの順番で、どの条件で、どこまで処理するか」を設計することです。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); // 本筋
}
Java

switch と 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、状態機械、メソッド分割で「構造化」してネストを消す。
  • 契約: 例外はバグや契約違反、早期リターンは想定内の不成立。意図をコードに刻む。

タイトルとURLをコピーしました