Java 入門

Java switch 文

switch 文は、プログラムが単一の変数の値に基づいて異なるコードブロックを実行するための、非常にエレガントな手法を提供します。特定の固定された既知の値を処理する必要がある場合、冗長になりがちな if-else if-else チェーンに代わる完璧な選択肢となります。

switch 文をマスターすることは、効率的で読みやすいコードを書くために不可欠です。高度に構造化された方法で多重分岐に対応できるようになります。

1. switch 文を理解する

Java の switch 文は、一つの式(通常は変数)の値に基づいて、複数のコードブロックから実行するものを一つ選択します。この式は一度だけ評価され、その値が各 case ラベルで指定された値と順番に照合(マッチング)されます。一致するものが見つかれば、その case に属するコードブロックが実行されます。

1.1 基本的な構文構造

switch 文の基本構文は以下の通りです。

switch (expression) {
    case value1:
        // expression == value1 の場合に実行されるコード;
        break;
    case value2:
        // expression == value2 の場合に実行されるコード;
        break;
    case valueN:
        // expression == valueN の場合に実行されるコード;
        break;
    default:
        // expression がどの case にも一致しない場合に実行されるデフォルトのコード;
}
  • switch (expression): 括弧内の expression (式) はチェックしたいターゲットです。データ型は byteshortintcharString、または enum (列挙型) に厳格に制限されています。
  • case value1:: 各 case ラベルは具体的な値を指定します。式の値が value1 と完全に一致した場合、プログラムはこのラベルから下のコードを実行します。
  • break;: break ステートメントは極めて重要です。その役割は switch 文を即座に終了させ、コードが次の case へ「フォールスルー(突き抜け)」するのを防ぐことです。これがないと、プログラムは以降の条件一致を無視して、次の case 内のコードを直接実行してしまいます。
  • default:: default ラベルはオプショナル(任意)ですが、追加することを強く推奨します。式の値が上記のどの case にも一致しない場合、プログラムは default 以降のコードを実行します。これは if-else における最後の else に似ています。

2. break の重要性:フォールスルー現象 (Fall-through) に注意

break ステートメントは switch 内部の実行フローを制御するスイッチです。これがないと、プログラムは フォールスルー (Fall-through) 現象を起こし、たとえ次の case の値が一致していなくても、そのまま次のコードを実行してしまいます。

極めて稀なギーク向けのシナリオでは意図的に利用されることもありますが、日常的な開発の 99% において、break の書き忘れは重大な バグ (Bug) の原因となります。

誤った例:

int day = 2;
String dayType;

switch (day) {
    case 1:
        dayType = "月曜日";
    case 2:
        dayType = "火曜日";
    case 3:
        dayType = "水曜日";
    default:
        dayType = "平日";
}

System.out.println(dayType); // 出力: 平日 (バグ!フォールスルーが発生)

解析: この例では break ステートメントがないため、コードが case 2 にマッチした後、dayType に "火曜日" が代入されます。しかしそこで止まらず、そのまま下の case 3 を実行し、最後に default まで実行されます。最終的に dayType は "平日" に上書きされてしまい、意図に反する結果となります。

正しい書き方:各 case の後に break を追加

int day = 2;
String dayType;

switch (day) {
    case 1:
        dayType = "月曜日";
        break; // マッチして実行された後、即座に switch を抜ける
    case 2:
        dayType = "火曜日";
        break;
    case 3:
        dayType = "水曜日";
        break;
    default:
        dayType = "平日";
}

System.out.println(dayType); // 出力: 火曜日 (完璧です!)

3. switch がサポートするデータ型の実践

前述の通り、switch の式は特定のデータ型のみをサポートします。具体的な実戦デモンストレーションを見てみましょう。

3.1 整数型 (int)

int month = 4;
String monthString;

switch (month) {
    case 1:  monthString = "1月"; break;
    case 2:  monthString = "2月"; break;
    case 3:  monthString = "3月"; break;
    case 4:  monthString = "4月"; break;
    // ... 5月から11月までを省略 ...
    case 12: monthString = "12月"; break;
    default: monthString = "無効な月";
}
System.out.println(monthString); // 出力: 4月

3.2 文字型 (char)

char grade = 'B';
String gradeDescription;

switch (grade) {
    case 'A': gradeDescription = "優秀"; break;
    case 'B': gradeDescription = "良好"; break;
    case 'C': gradeDescription = "普通"; break;
    case 'D': gradeDescription = "合格"; break;
    case 'F': gradeDescription = "不合格"; break;
    default: gradeDescription = "無効なグレード";
}
System.out.println(gradeDescription); // 出力: 良好

3.3 文字列型 (String)

String day = "Monday";
String activity;

switch (day) {
    case "Monday":    activity = "週の始まり"; break;
    case "Tuesday":   activity = "2日目"; break;
    case "Wednesday": activity = "週の真ん中"; break;
    case "Thursday":  activity = "もう少しで週末"; break;
    case "Friday":    activity = "週末まであと少し"; break;
    default:          activity = "週末の予定";
}
System.out.println(activity); // 出力: 週の始まり

3.4 列挙型 (enum)

まず、クラスの外部または内部で enum(列挙型) を定義します。

enum Day {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

そして、switch 内でスムーズに使用します。

Day today = Day.WEDNESDAY;
String message;

switch (today) {
    case MONDAY:    message = "新しい一週間の始まり"; break;
    case TUESDAY:   message = "仕事2日目"; break;
    case WEDNESDAY: message = "週の折り返し地点です!"; break;
    case THURSDAY:  message = "木曜日、頑張りましょう"; break;
    case FRIDAY:    message = "TGIF! 今日は金曜日です!"; break;
    case SATURDAY:  message = "週末万歳!"; break;
    case SUNDAY:    message = "のんびりした日曜日"; break;
    default:        message = "無効な日";
}
System.out.println(message); // 出力: 週の折り返し地点です!

4. デフォルト分岐:default の役割

default 分岐は switch 文のオプショナルな部分です。すべての case がターゲット値と一致しない場合、この「フォールバック / 後備」オプションが提供されます。default を書く習慣をつけることで、無効な入力や未知の例外状況をエレガントに処理でき、コードを極めて 堅牢 (ロバスト) に保つことができます。

int number = 10;
String result;

switch (number) {
    case 1:  result = "一"; break;
    case 2:  result = "二"; break;
    case 3:  result = "三"; break;
    default: result = "その他の数字";
}
System.out.println(result); // 出力: その他の数字 (10 は case に存在しないため)

5. 高度な機能:Switch 式 (Java 12+)

Java 12 以降を使用している場合、switch は劇的に強化され、switch 式 (Switch Expressions) が導入されました。これによりコードがよりコンパクトになるだけでなく、値を直接返すことができるようになりました。

int day = 2;
// switch の結果を直接 dayType 変数に代入
String dayType = switch (day) {
    case 1 -> "月曜日";
    case 2 -> "火曜日";
    case 3 -> "水曜日";
    default -> "平日";
};

System.out.println(dayType); // 出力: 火曜日

switch 式の圧倒的なメリット:

  • アローラベル (->): 従来のコロン : に代わって使用されます。
  • break からの解放: アロー構文は本質的にフォールスルーを防止します。マッチングが完了すると値を返して終了するため、手動で break を書く必要がありません。
  • 網羅性の強制 (Exhaustiveness): コンパイラがすべてのケースを処理しているか厳格にチェックします(enum の場合は全列挙、通常の型の場合は default が必須)。これによりロジックの漏れを排除できます。
  • 値を直接返す: アローの右側がその式の戻り値となります。

マッチング後に複雑なコード(複数行)を実行する必要がある場合は、波括弧 {} を使用し、yield キーワードを使用して値を返します。

int day = 5;
String dayType = switch (day) {
    case 1 -> "月曜日";
    case 2 -> "火曜日";
    case 3 -> "水曜日";
    case 4 -> "木曜日";
    case 5 -> {
        System.out.println("もうすぐ週末です!");
        yield "金曜日"; // yield キーワードを使用して値を返す
    }
    default -> "無効な日";
};
System.out.println(dayType); 
// 出力:
// もうすぐ週末です!
// 金曜日

6. 実戦演習とデモンストレーション

知識を定着させるために、いくつかのリアルなビジネスシナリオのコードを見てみましょう。

6.1 例 1:簡易計算機

switch を利用して基本的な数学演算子を処理します。

import java.util.Scanner;

public class Calculator {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        System.out.print("最初の数字を入力してください: ");
        double num1 = scanner.nextDouble();
        
        System.out.print("演算子 (+, -, *, /) を入力してください: ");
        char operator = scanner.next().charAt(0);
        
        System.out.print("2番目の数字を入力してください: ");
        double num2 = scanner.nextDouble();
        
        double result = 0.0;
        
        switch (operator) {
            case '+':
                result = num1 + num2;
                break;
            case '-':
                result = num1 - num2;
                break;
            case '*':
                result = num1 * num2;
                break;
            case '/':
                if (num2 != 0) {
                    result = num1 / num2;
                } else {
                    System.out.println("エラー:ゼロで割ることはできません。");
                    return; // プログラム終了
                }
                break;
            default:
                System.out.println("無効な演算子です。");
                return;
        }
        
        System.out.println("計算結果: " + result);
        scanner.close();
    }
}

6.2 例 2:フォールスルーを利用した分岐の統合 (季節の判定)

フォールスルーはバグの原因と言いましたが、あえて利用することで複数の異なる値で同一のコードを共有させることができます。

public class SeasonChecker {
    public static void main(String[] args) {
        int month = 8;
        String season;
        
        switch (month) {
            case 12: case 1: case 2: // 12, 1, 2月をまとめて処理
                season = "冬";
                break;
            case 3: case 4: case 5:
                season = "春";
                break;
            case 6: case 7: case 8:
                season = "夏";
                break;
            case 9: case 10: case 11:
                season = "秋";
                break;
            default:
                season = "無効な月";
        }
        System.out.println(month + "月は " + season + " です。"); // 出力: 8月は 夏 です。
    }
}

6.3 例 3:カンマ区切りのモダンな Switch (週末の判定)

Java 14 以降、新しい switch 式を使用する場合、カンマを使用して複数の値を一行に記述でき、フォールスルーを利用するよりも直感的です。

public class DayTypeChecker {
    public static void main(String[] args) {
        int day = 6; // 1: 月曜, 2: 火曜, ..., 7: 日曜
        
        String dayType = switch (day) {
            case 1, 2, 3, 4, 5 -> "平日";
            case 6, 7 -> "休日";
            default -> "無効な日付";
        };
        
        System.out.println(day + "日目は " + dayType + " です。"); // 出力: 6日目は 休日 です。
    }
}