無名パターンおよび変数

無名パターンは、レコード・パターンのパターン・リストに出現することがあり、対応するレコード・コンポーネントと常に一致します。これらは型パターンのかわりに使用できます。後続のコードでは必要のないパターン変数の型と名前を記述する必要がなくなります。無名変数は、初期化はできるが使用できない変数です。両方ともアンダースコア文字(_)で示します。

ノート:

これはプレビュー機能です。プレビュー機能は、設計、仕様および実装が完了したが、永続的でない機能です。プレビュー機能は、将来のJava SEリリースで、異なる形式で存在することもあれば、まったく存在しないこともあります。プレビュー機能が含まれているコードをコンパイルして実行するには、追加のコマンド行オプションを指定する必要があります。『Preview Language and VM Features』を参照してください。

無名パターンと変数の背景情報は、JEP 443を参照してください。

無名パターン

ColoredPointの2つのインスタンス間の距離を計算する次の例を考えてみます。

    record Point(double x, double y) {}
    enum Color { RED, GREEN, BLUE }
    record ColoredPoint(Point p, Color c) {}

    double getDistance(Object obj1, Object obj2) {
        if (obj1 instanceof ColoredPoint(Point p1, Color c1) &&
            obj2 instanceof ColoredPoint(Point p2, Color c2)) {
        return java.lang.Math.sqrt(
            java.lang.Math.pow(p2.x - p1.x, 2) +
            java.lang.Math.pow(p2.y - p1.y, 2));
        } else {
            return -1;
        }
    }

この例では、ColoredPointレコードのColorコンポーネントは使用しません。コードを簡略化し、読みやすさを向上させるには、無名パターン(_)を使用してタイプ・パターンColor c1およびColor c2を省略または削除できます。

    double getDistance(Object obj1, Object obj2) {
        if (obj1 instanceof ColoredPoint(Point p1, _) &&
            obj2 instanceof ColoredPoint(Point p2, _)) {
        return java.lang.Math.sqrt(
            java.lang.Math.pow(p2.x - p1.x, 2) +
            java.lang.Math.pow(p2.y - p1.y, 2));
        } else {
            return -1;
        }
    }

または、型パターンの型を保持し、名前だけを削除することもできます。

        if (obj1 instanceof ColoredPoint(Point p1, Color _) &&
            obj2 instanceof ColoredPoint(Point p2, Color _))

無名パターン変数には値がバインドされていません。したがって、次の例で強調表示された文は無効です。

        if (obj1 instanceof ColoredPoint(Point p1, Color _) &&
            obj2 instanceof ColoredPoint(Point p2, Color _)) {
            // Compiler error: the underscore keyword '_" is only allowed to
            // declare unnamed patterns, local variables, exception parameters or
            // lambda parameters
            System.out.println("Color: " + _); 
            // ...
        }   

また、無名パターンを最上位レベルのパターンとして使用することはできません。

        // Compiler error: the underscore keyword '_' is only allowed to
        // declare unnamed patterns, local variables, exception parameters or
        // lambda parameters        
        if (obj1 instanceof _) {
            // ...
        }

無名パターンをswitch式および文で使用できます。

    sealed interface Employee permits Salaried, Freelancer, Intern { }
    record Salaried(String name, long salary) implements Employee { }
    record Freelancer(String name) implements Employee { }
    record Intern(String name) implements Employee { }
        
    void printSalary(Employee b) {
        switch (b) {
            case Salaried r   -> System.out.println("Salary: " + r.salary());
            case Freelancer _ -> System.out.println("Other");
            case Intern _     -> System.out.println("Other");
        }
    }

パターン変数を宣言していない場合は、caseラベルに複数のパターンを使用できます。たとえば、前のswitch文を次のようにリライトできます。

        switch (b) {
            case Salaried r              -> System.out.println("Salary: " + r.salary());
            case Freelancer _, Intern _  -> System.out.println("Other");
        }

無名変数

アンダースコア・キーワード(_)は、パターン・リストのパターンとしてだけでなく、宣言の値が不要な場合の宣言でローカル変数、例外またはラムダ・パラメータの名前としても使用できます。これは無名変数と呼ばれ、宣言されている変数を表しますが、使用可能な名前はありません。

無名変数は、文の副次的効果が結果よりも重要である場合に役立ちます。

forループを使用して配列orderIDsの要素を反復する次の例を考えてみます。このforループの副次的効果は、ループ変数idを使用せずにorderIDsの要素数を計算することです。

        int[] orderIDs = {34, 45, 23, 27, 15};
        int total = 0;
        for (int id : orderIDs) {
            total++;
        }
        System.out.println("Total: " + total);

無名変数を使用して、使用されていない変数idを削除できます。

        int[] orderIDs = {34, 45, 23, 27, 15};
        int total = 0;
        for (int _ : orderIDs) {
            total++;
        }
        System.out.println("Total: " + total);

次の表に、無名変数を宣言できる場所を示します。

表6-1 有効な無名変数宣言

宣言タイプ 無名変数を使用した例
ブロック内のローカル変数宣言文
record Caller(String phoneNumber) { }

static List everyFifthCaller(Queue<Caller> q, int prizes) {
    var winners = new ArrayList<Caller>();
    try {
        while (prizes > 0) {
            Caller _ = q.remove();
            Caller _ = q.remove();
            Caller _ = q.remove();
            Caller _ = q.remove();
            winners.add(q.remove());
            prizes--;
        }
    } catch (NoSuchElementException _) {
        // Do nothing
    }
    return winners;
}

Queue::removeによって返される値を名前付き変数または無名変数に割り当てる必要はありません。あまり知られていないAPIによってアプリケーションで使用しない値が返されることを示すために、そうすることができます。

try-with-resources文のリソース指定
static void doesFileExist(String path) {
    try (var _ = new FileReader(path)) {
        // Do nothing
    } catch (IOException e) {
        e.printStackTrace();
    }
}	
基本のfor文のヘッダー
Function<String,Integer> sideEffect =
    s -> {
        System.out.println(s);
        return 0;
    };
    
for (int i = 0, _ = sideEffect.apply("Starting for-loop");
    i < 10; i++) {
    System.out.println(i);
}
拡張forループのヘッダー
static void stringLength(String s) {
    int len = 0;
    for (char _ : s.toCharArray()) {
        len++;
    }
    System.out.println("Length of " + s + ": " + len);
}
catchブロックの例外パラメータ
static void validateNumber(String s) {
    try {
        int i = Integer.parseInt(s);
        System.out.println(i + " is valid");
    } catch (NumberFormatException _) {
        System.out.println(s + " isn't valid");
    }
} 
ラムダ式の仮パラメータ
record Point(double x, double y) {}
record UniqueRectangle(String id,
    Point upperLeft, Point lowerRight) {}       
    
static Map getIDs(List<UniqueRectangle> r) {
    return r.stream()
            .collect(
                Collectors.toMap(
                    UniqueRectangle::id,
                    _ -> "NODATA"));
}