レコードとかswitch式とかパターンマッチとかテキストブロックとかその辺のまとめ

Java 11 から Java 21 までの間に追加された以下の言語仕様について、一通り正式採用されたっぽくて LTS も出たので整理する。

- switch式
- テキストブロック
- Pattern Matching for instanceof
- レコードクラス
- シールクラス
- レコードパターン
- Pattern Matching for switch

# 環境
```text
> java --version
openjdk 21.0.1 2023-10-17 LTS
OpenJDK Runtime Environment Temurin-21.0.1+12 (build 21.0.1+12-LTS)
OpenJDK 64-Bit Server VM Temurin-21.0.1+12 (build 21.0.1+12-LTS, mixed mode, sharing)
```

# 各言語仕様の正式採用バージョン整理

|バージョン|正式採用された言語仕様|
|---------|-------|
|11 | |
|12 | |
|13 | |
|14 |switch式 |
|15 |テキストブロック |
|16 |Pattern Matching for instanceof<br>レコードクラス |
|17 |シールクラス |
|18 | |
|19 | |
|20 | |
|21 |レコードパターン<br>Pattern Matching for switch |

# レコードクラス(Record Class)
DDD の値クラスみたいなのを簡単に作れるようにしたもの。
アクセサや `toString`, `equals` の実装など、ありがちなボイラープレートを書かずに簡単にデータを入れるためのクラスを作成できる。

## Hello World
```java
public record MyRecord(int number, String text) {
}
```

```java
public class RecordTest {

    public static void main(String[] args) {
        MyRecord myRecord = new MyRecord(1, "test");
        System.out.println(myRecord);
    }
}
```

```text:実行結果
MyRecord[number=1, text=test]
```

### 説明
```java
public record MyRecord(int number, String text) {
}
```

- レコードを定義する場合は、 `class` の代わりに `record` で型を定義する
- クラス名の直後に、レコードに持たせるフィールドを宣言する
    - この部分を**レコードヘッダー**と呼ぶ
        - 上の実装例だと `(int number, String text)` の部分
    - 個々のフィールドに対応する宣言を**レコードコンポーネント**と呼ぶ
        - 上の実装例だと `int number`, `String text` の部分
    - 参考: [8.10. Record Classes | The Java Language Specification Java SE 21 Edition](https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html#jls-8.10)
- 以上のコードで、以下のクラスを定義したのと同じになる

```java:Recordで生成されるクラス
public final class MyRecord {
    private final int number;
    private final String text;

    public MyRecord(int number, String text) {
        this.number = number;
        this.text = text;
    }

    public int number() {
        return number;
    }

    public String text() {
        return text;
    }

    @Override
    public String toString() {
        return "MyRecord[number=" + number + ", text=" + text + "]";
    }

    @Override
    public int hashCode() {
        // number, text の値を元にハッシュ値を計算
    }

    @Override
    public boolean equals(Object other) {
        // 型が等しく、number, textが等しい場合はtrueを返す
    }
}
```

- 以下の実装が自動的に定義される
    - `final` なクラス
    - レコードコンポーネントに対応する `final` なフィールド
    - フィールドの値を全て受け取り初期化するコンストラクタ
    - 各フィールドと同名のゲッター
    - 全てのフィールドを名前と共に出力する `toString`
    - 全てのフィールドでハッシュ値を計算する `hashCode`
    - 型が等しく全てのフィールドの値が等しい場合に `true` を返す `equals`
- フィールドの宣言は空にすることもできる（実用性はなさそうだが）

```java
public record MyRecord() {
}
```

- この場合でも `()` は省略できない（`()` を省略するとコンパイルエラー）

## コンパクト・コンストラクタ

```java
public record MyRecord(int number, String text) {
    public MyRecord {
        System.out.println("Compact Constructor : number=" + number + ", text=" + text);
    }
}
```

```java
public class RecordTest {

    public static void main(String[] args) {
        new MyRecord(10, "test");
    }
}
```

```text:実行結果
Compact Constructor : number=10, text=test
```

- 仮引数を省略する形でコンストラクタを宣言できる
- これを **コンパクト・コンストラクタ** と呼ぶ
- コンパクト・コンストラクタは、以下のような実装から最後のフィールドへの代入を省略したものと同じになる

```java:コンパクト・コンストラクタと同等のコンストラクタ
public MyRecord(int number, String text) {
    ...
    this.number = number;
    this.text = text;
}
```

## コンストラクタをオーバーロードできる
```java
public record MyRecord(int number, String text) {
    
    public MyRecord(int number) {
        this(number, "MyRecord(int)");
    }

    public MyRecord(String text) {
        this(-1, text);
    }

    public MyRecord() {
        this(-1, "MyRecord()");
    }
}
```

- コンストラクタはオーバーロードが可能
- コンパクト・コンストラクタとの併用も可能

## static 要素を宣言できる

```java
public record MyRecord(String text) {

    static {
        System.out.println("Static Initializer");
    }

    public static void staticMethod() {
        System.out.println("Static Method. STATIC_FIELD=" + STATIC_FIELD);
    }

    public final static String STATIC_FIELD = "Static Field";
}
```

```java
public class RecordTest {

    public static void main(String[] args) {
        MyRecord.staticMethod();
    }
}
```

```text:実行結果
Static Initializer
Static Method. STATIC_FIELD=Static Field
```

- static イニシャライザ、static メソッド、static フィールドは通常のクラスと同様に宣言できる

## インスタンス・イニシャライザは宣言できない
```java:コンパイルエラー
public record MyRecord(String value) {
    {
        System.out.println("これは実装できない");
    }
}
```

- レコードクラスではインスタンス・イニシャライザは宣言できない

## 自動生成されるメソッドをオーバーライドできる
```java
public record MyRecord(String text) {

    @Override
    public String text() {
        return "<<" + text + ">>";
    }
}
```

```java
public class RecordTest {

    public static void main(String[] args) {
        MyRecord myRecord = new MyRecord("test");
        System.out.println(myRecord.text());
    }
}
```

```text:実行結果
<<test>>
```

- アクセサや `toString` など、自動生成されるメソッドは自由にオーバーライドできる

## 任意のメソッドを定義できる

```java
public record MyRecord(String text) {

    public void hello() {
        System.out.println("Hello " + text);
    }
}
```

```java
public class RecordTest {

    public static void main(String[] args) {
        MyRecord myRecord = new MyRecord("test");
        myRecord.hello();
    }
}
```

```text:実行結果
Hello test
```

- 自動生成されるメソッド以外でも自由にメソッドを追加できる

## 総称型にできる
```java
public record MyRecord<T> (T value) {
}
```

```java
public class RecordTest {

    public static void main(String[] args) {
        MyRecord<String> myRecord = new MyRecord<>("test");
        System.out.println(myRecord);
    }
}
```

```text:実行結果
MyRecord[value=test]
```

- レコードクラスは総称型にできる

## インタフェースを実装できる
```java
public record MyRecord(String value) implements Runnable {
    @Override
    public void run() {
        System.out.println("MyRecord.run()");
    }
}
```

```java
public class RecordTest {

    public static void main(String[] args) {
        Runnable myRecord = new MyRecord("test");
        myRecord.run();
    }
}
```

```text:実行結果
MyRecord.run()
```

- レコードクラスは通常のクラス同様にインタフェースを実装できる

## クラスの継承はできない
```java:コンパイルエラー
public record MyRecord(String value) extends Number {
}
```

- レコードクラスは他のクラスを継承することはできない

## レコードコンポーネントとアノテーション

```java:MyFieldAnnotation
package java21.record;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyFieldAnnotation {
}
```

- `Target` に `FIELD` を設定したアノテーション

```java:MyRecordComponentAnnotation
package java21.record;

...

@Target(ElementType.RECORD_COMPONENT)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRecordComponentAnnotation {
}
```

- `Target` に `RECORD_COMPONENT` を設定したアノテーション

```java:MyMethodAnnotation
package java21.record;

...

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyMethodAnnotation {
}
```

- `Target` に `METHOD` を設定したアノテーション

```java:MyParameterAnnotation
package java21.record;

...

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyParameterAnnotation {
}
```

- `Target` に `PARAMETER` を設定したアノテーション

```java:MyTypeUseAnnotation
package java21.record;

...

@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTypeUseAnnotation {
}
```

- `Target` に `TYPE_USE` を設定したアノテーション




```java:MyRecord
public record MyRecord(
        @MyFieldAnnotation
        @MyRecordComponentAnnotation
        @MyParameterAnnotation
        @MyMethodAnnotation
        @MyTypeUseAnnotation
        String value) {
}
```

- レコードコンポーネントに、上で定義した5つのアノテーションを設定している

```java
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.RecordComponent;
import java.util.Arrays;

public class RecordTest {

    public static void main(String[] args) throws Exception {
        Field field = MyRecord.class.getDeclaredField("value");
        printAnnotations("Field", field, field.getAnnotatedType());

        for (RecordComponent recordComponent : MyRecord.class.getRecordComponents()) {
            printAnnotations("RecordComponent", recordComponent,
                    recordComponent.getAnnotatedType());
        }

        Constructor<MyRecord> constructor = MyRecord.class.getConstructor(String.class);
        for (Parameter parameter : constructor.getParameters()) {
            printAnnotations("Constructor.Parameter", parameter,
                    parameter.getAnnotatedType());
        }

        Method valueAccessor = MyRecord.class.getMethod("value");
        printAnnotations("Method", valueAccessor,
                valueAccessor.getAnnotatedReturnType());
    }

    private static void printAnnotations(String tag, AnnotatedElement ae,
                                         AnnotatedType annotatedType) {
        System.out.println("=== " + tag + " ===");
        System.out.println("  annotations = " + Arrays.toString(ae.getAnnotations()));
        System.out.println("  annotatedType.annotations = "
                + annotatedTypeAnnotations(annotatedType));
    }

    private static String annotatedTypeAnnotations(AnnotatedType annotatedType) {
        return Arrays.toString(annotatedType.getAnnotations());
    }
}
```

- `MyRecord` からリフレクションを使って各アノテーションの情報を出力している

```text:実行結果
=== Field ===
  annotations = [@java21.record.MyFieldAnnotation()]
  annotatedType.annotations = [@java21.record.MyTypeUseAnnotation()]
=== RecordComponent ===
  annotations = [@java21.record.MyRecordComponentAnnotation()]
  annotatedType.annotations = [@java21.record.MyTypeUseAnnotation()]
=== Constructor.Parameter ===
  annotations = [@java21.record.MyParameterAnnotation()]
  annotatedType.annotations = [@java21.record.MyTypeUseAnnotation()]
=== Method ===
  annotations = [@java21.record.MyMethodAnnotation()]
  annotatedType.annotations = [@java21.record.MyTypeUseAnnotation()]
```

### 説明
```java
public record MyRecord(
        @MyFieldAnnotation
        @MyRecordComponentAnnotation
        @MyParameterAnnotation
        @MyMethodAnnotation
        @MyTypeUseAnnotation
        String value) {
}
```

- レコードコンポーネントには、 `@Target` で以下のいずれかを指定したアノテーションを設定できる
    - `FIELD`
    - `RECORD_COMPONENT`
    - `PARAMETER`
    - `METHOD`
    - `TYPE_USE`
- 設定されたアノテーションは、自動生成される各要素に分配される
    - `FIELD`
        - 自動生成されるフィールドに設定される
    - `RECORD_COMPONENT`
        - レコードコンポーネントにだけ設定される
    - `PARAMETER`
        - 自動生成されるコンストラクタのパラメータに設定される
    - `METHOD`
        - 自動生成されるアクセサに設定される
    - `TYPE_USE`
        - 自動生成される各要素の型宣言部分に設定される
- アノテーションの展開のイメージは、おそらく以下のような感じ
    - あくまでイメージ

```java
public class MyRecord (@MyRecordComponentAnnotation @MyTypeUseAnnotation String value) {
    @MyFieldAnnotation
    @MyTypeUseAnnotation
    private final String value;

    public MyRecord(@MyParameterAnnotation @MyTypeUseAnnotation String value) {
        this.value = value;
    }

    @MyMethodAnnotation
    @MyTypeUseAnnotation
    public String value() {
        return value;
    }
}
```

## ミュータブルなコンポーネントを宣言した場合
```java
import java.util.List;

public record MyRecord(List<String> list) {
}
```

```java
import java.util.ArrayList;
import java.util.List;

public class RecordTest {

    public static void main(String[] args) {
        List<String> list = new ArrayList<>(List.of("foo", "bar"));
        MyRecord myRecord = new MyRecord(list);
        System.out.println("before : " + myRecord);
        list.add("FIZZ");
        System.out.println("after : " + myRecord);
    }
}
```

```text:実行結果
before : MyRecord[list=[foo, bar]]
after : MyRecord[list=[foo, bar, FIZZ]]
```

フィールドが `final` で宣言されてセッターも宣言されないので一見イミュータブルっぽいが、コンポーネントをミュータブルにしていると外部から状態を変更できるので注意が必要。

# switch式
## Hello World
```java
public class SwitchExpressionTest {
    public static void main(String[] args) {
        for (int i=0; i<6; i++) {
            try {
                System.out.println(hello(i));
            } catch (IllegalArgumentException e) {
                System.out.println("e.message=" + e.getMessage());
            }
        }
    }

    private static String hello(int n) {
        return switch (n) {
            case 0 -> "ZERO";
            case 1, 2 -> "ONE or TWO";
            case 3 -> throw new IllegalArgumentException("n=3");
            case 4 -> {
                System.out.print("n=4 ");
                yield "FOUR";
            }
            default -> "OTHER";
        };
    }
}
```

```text:実行結果
ZERO
ONE or TWO
ONE or TWO
e.message=n=3
n=4 FOUR
OTHER
```

### 説明
```java
return switch (n) {
    case 0 -> "ZERO";
    case 1, 2 -> "ONE or TWO";
    case 3 -> throw new IllegalArgumentException("n=3");
    case 4 -> {
        System.out.print("n=4 ");
        yield "FOUR";
    }
    default -> "OTHER";
};
```

- switch式は、名前の通り switch が式として評価されて値を返す
- case ラベルが従来の switch文と異なり以下のように書ける
    - `case label[, label...] -> expression; | throw-statement; | block`
- 1つの `case` の中でカンマ区切りで複数のラベルを列挙できる
- フォール・スルーを防ぐための `break` は不要
- 複数行のコードを書きたい場合はブロックを書ける
    - その場合、 switch式の結果となる値は `yield 値` という形で記述する
- 入力された値が取りえる値を全てラベルとして列挙する必要があるため、基本的に `default` ラベルは必須となる

## null ケースラベル
```java
public class SwitchExpressionTest {
    public static void main(String[] args) {
        System.out.println(hello(null));
    }

    private static String hello(String s) {
        return switch (s) {
            case "a" -> "A";
            default -> "DEFAULT";
        };
    }
}
```

```text:実行結果
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.hashCode()" because "<local1>" is null
	at java21.switchexp.SwitchExpressionTest.hello(SwitchExpressionTest.java:9)
	at java21.switchexp.SwitchExpressionTest.main(SwitchExpressionTest.java:5)
```

- `default` ラベルがあっても、 `null` を渡すと実行時に `NullPointerException` になる
- `null` の場合も処理したい場合は、**nullケースラベル**を追加する

```java
public class SwitchExpressionTest {
    public static void main(String[] args) {
        System.out.println(hello(null));
    }

    private static String hello(String s) {
        return switch (s) {
            case "a" -> "A";
            case null -> "NULL"; // ★nullケースラベル
            default -> "DEFAULT";
        };
    }
}
```

```text:実行結果
NULL
```

- `case null ->` というケースを追加することで `null` をハンドリングできる
- `null` とデフォルトを同じ操作にしたい場合は、以下のように書く

```java
public class SwitchExpressionTest {
    public static void main(String[] args) {
        System.out.println(hello(null));
    }

    private static String hello(String s) {
        return switch (s) {
            case "a" -> "A";
            case null, default -> "DEFAULT";
        };
    }
}
```

```text:実行結果
DEFAULT
```

- `case null, default` のように、 `default` を `case` のラベルに指定できるようになっている


## 列挙型を渡す場合
```java
public class SwitchExpressionTest {
    public static void main(String[] args) {
        System.out.println(hello(MyEnum.HOGE));
    }

    private static String hello(MyEnum e) {
        return switch (e) {
            case HOGE -> "hoge";
            case FUGA -> "fuga";
            case PIYO -> "piyo";
        };
    }

    enum MyEnum {
        HOGE,
        FUGA,
        PIYO,
    }
}
```

```text:実行結果
hoge
```

- switch式に列挙型の値を渡す場合、全ての列挙子をラベルに挙げれば `default` ラベルは不要となる

# シールクラス (Sealed Classes)
英語だと Sealed Classes だけど、日本語表記だとシールクラスになるっぽい。
(シールドだと shield(盾) のイメージが強いから？ seal は「密封、密閉」といった意味)

[シール・クラス | Java言語更新](https://docs.oracle.com/javase/jp/17/language/sealed-classes-and-interfaces.html#GUID-0C709461-CC33-419A-82BF-61461336E65F)

## Hello World
```java:MyClass
public sealed class MyClass permits Hoge, Fuga{
}
```

```java:Hoge
public final class Hoge extends MyClass {
}
```

```java:Fuga
public final class Fuga extends MyClass {
}
```

```java:Piyo(コンパイルエラー)
// 'Piyo' is not allowed in the sealed hierarchy
public final class Piyo extends MyClass {
}
```

```plantuml
class MyClass <<sealed>> {}
note right of MyClass
permits Hoge, Fuga
end note

class Hoge <<final>> extends MyClass {}
class Fuga <<final>> extends MyClass {}
class Piyo <<final>> extends MyClass {}
note bottom of Piyo
※コンパイルエラー
end note
```

- クラスの修飾子に `sealed` を付けることで、そのクラスをシールクラスにできる
- シールクラスは、そのクラスを継承できるクラスを限定できる
    - クラス名の後の `permits` で、継承できるクラスを列挙する
    - ここでは `Hoge`, `Fuga` クラスだけが継承できるように許可している
    - `Piyo` は継承を許可されていないので、 `MyClass` を継承しようとするとコンパイルエラーになる
- `permits` で継承を許可されたクラスはシールクラスを継承できる
    - シールクラスを継承したクラスは、以下のいずれかの修飾子を付ける必要がある
    - `final`
        - それ以上の継承を許さない場合は `final` をつける
    - `sealed`
        - さらにサブクラスの型を限定する場合は `sealed` をつける
    - `non-sealed`
        - サブクラスの型を限定しない場合は `non-sealed` をつける

## 継承を許可するクラスを配置できる場所
- `permits` に列挙するクラスは、コンパイル時にシールクラスからアクセス可能な場所に存在しなければならない
- また、シールクラスが名前付きモジュールの中に存在する場合は、許可されるクラスは全て同じモジュール内にいなければならない
- シールクラスが名前付きモジュールの中に存在しない場合は、許可されるクラスは全て同じパッケージ内に存在する必要がある

```text:フォルダ構成
src/main/java/
  `-java21/sealed/
    |-MyClass.java
    |-Hoge.java
    `-sub/
      `-Foo.java
```

```java:MyClass(コンパイルエラー)
package java21.sealed;

// Class is not allowed to extend sealed class from another package
public sealed class MyClass permits Hoge, java21.sealed.sub.Foo {
}
```

```java:Hoge
package java21.sealed;

public final class Hoge extends MyClass {
}
```

```java:Foo
package java21.sealed.sub;

import java21.sealed.MyClass;

public final class Foo extends MyClass {
}
```

```plantuml
package "java21.sealed" {
    class MyClass <<sealed>> {}
    note right of MyClass
    permits Hoge, java21.sealed.sub.Foo
    ※コンパイルエラー
    end note
    class Hoge <<final>> extends MyClass {}

    package "sub" {
        class Foo <<final>> {}
    }

    MyClass <|-- Foo
}
```


- シールクラスが存在するプロジェクトには `module-info.java` がないので、無名モジュールとなっている
- シールクラスである `MyClass` は、異なるパッケージに存在する `Foo` を `permits` に指定している
- しかし、名前付きモジュールでない場合は別パッケージのクラスを `permits` に指定できないので、コンパイルエラーとなっている
- これのコンパイルを通すためには、シールクラスである `MyClass` が存在するプロジェクトを名前付きモジュールにする必要がある

```text:フォルダ構成
src/main/java/
  |-module-info.java ★追加
  `-java21/sealed/
    |-MyClass.java
    |-Hoge.java
    `-sub/
      `-Foo.java
```

```java:module-info.java
module test {
}
```

- これでコンパイルエラーが無くなる

## sealed を連鎖させる
```java:MyClass
public sealed class MyClass permits Hoge {
}
```

```java:Hoge
public sealed class Hoge extends MyClass permits Fuga, Piyo {
}
```

```java:Fuga
public final class Fuga extends Hoge {
}
```

```java:Piyo
public final class Piyo extends Hoge {
}
```

```plantuml
class MyClass <<sealed>>
note right of MyClass
permits Hoge
end note

class Hoge <<sealed>> extends MyClass
note right of Hoge
permits Fuga, Piyo
end note

class Fuga <<final>> extends Hoge
class Piyo <<final>> extends Hoge
```

- `permits` で継承を許可したクラスに `sealed` を付けることで、さらにそのサブタイプを限定させることができる

## 任意のクラスが継承できるようにする
```java:MyClass
public sealed class MyClass permits Hoge, Fuga {
}
```

```java:Hoge
public final class Hoge extends MyClass {
}
```

```java:Fuga
public non-sealed class Fuga extends MyClass {
}
```

```java:Foo
public class Foo extends Fuga {
}
```

```plantuml
class MyClass <<sealed>>
note right of MyClass
permits Hoge, Fuga
end note

class Hoge <<final>> extends MyClass
class Fuga <<non-sealed>> extends MyClass
class Foo extends Fuga
```

- `permits` で継承を許可したクラスに `non-sealed` を付けると、そのクラスは任意のクラスで継承できるようになる

## シールインタフェース
```java:MyInterface
public sealed interface MyInterface permits Hoge, MyRecord {
}
```

```java:Hoge
public final class Hoge implements MyInterface {
}
```

```java:MyRecord
public record MyRecord() implements MyInterface {
}
```

```java:Piyo(コンパイルエラー)
// 'Piyo' is not allowed in the sealed hierarchy
public class Piyo implements MyInterface {
}
```

```plantuml
interface MyInterface <<sealed>>
note right of MyInterface
permits Hoge, Fuga
end note

class Hoge <<final>> implements MyInterface
class MyRecord <<record>> implements MyInterface
class Piyo <<final>> implements MyInterface
note bottom of Piyo
※コンパイルエラー
end note
```

- インタフェースにも `sealed` を付けて継承を制限させられる
- レコードクラスを `permits` に指定することも可能

# Pattern Matching for instanceof
## Hello World
```java
public class PatternMatchForInstanceof {

    public static void main(String[] args) {
        patternMatch("string");
        patternMatch(new Exception("exception"));
    }

    private static void patternMatch(Object obj) {
        if (obj instanceof String s) {
            System.out.println("s.upperCase = " + s.toUpperCase());
        } else if (obj instanceof Exception e) {
            System.out.println("e.message = " + e.getMessage());
        }
    }
}
```

```text:実行結果
s.upperCase = STRING
e.message = exception
```

### 説明
```java
    private static void patternMatch(Object obj) {
        if (obj instanceof String s) {
            System.out.println("s.upperCase = " + s.toUpperCase());
        } else if (obj instanceof Exception e) {
            System.out.println("e.message = " + e.getMessage());
        }
    }
```

- `instanceof` による型比較の後ろに変数を宣言できる
    - 上の例だと `s`, `e` が該当する
    - この変数を**パターン変数**と呼ぶ
- `instanceof` による比較が真だった場合、比較対象の値がパターン変数に代入されて利用できるようになる
- これにより、 `instanceof` で比較した後の明示的なキャストが不要となる

## パターン変数のスコープ
```java
    private static void patternMatch(Object obj) {
        if (obj instanceof String s) {
            System.out.println(s);
            System.out.println(e); // コンパイルエラー
        } else if (obj instanceof Exception e) {
            System.out.println(s); // コンパイルエラー
            System.out.println(e);
        } else {
            System.out.println(s); // コンパイルエラー
            System.out.println(e); // コンパイルエラー
        }

        System.out.println(s); // コンパイルエラー
        System.out.println(e); // コンパイルエラー
    }
```

- パターン変数が参照できるのは、 `instanceof` による比較が `true` となったときに通る場所に限られる
- したがって、次のように if 条件式の中で続けて参照することもできる

```java
    private static void patternMatch(Object obj) {
        if (obj instanceof String s && s.length() < 10) {
            System.out.println(s);
        }
    }
```

- 条件を `&&` でつなげているため、 `s.length() < 10` という形で同じ if 条件式の中で参照できる
    - `s.length() < 10` は `s instanceof String` が真である場合に評価されるため
- 一方で、以下のようなケースはコンパイルエラーになる

```java:コンパイルエラー
    private static void patternMatch(Object obj) {
        // Cannot resolve symbol 's'
        if (obj instanceof String s || s.length() < 10) {
            System.out.println(s);
        }
    }
```

- 条件が `||` でつなげられているので、 `s.length() < 10` は `obj instanceof String` が偽の場合も実行される可能性があり、パターン変数は参照できない
- `instanceof` の結果が真のときに参照できるという条件なので、次のような形でも書ける

```java
    private static void patternMatch(Object obj) {
        if (!(obj instanceof String s)) {
            System.out.println(s); // コンパイルエラー
            return;
        }
        System.out.println(s); // 参照可能
    }
```

- if の条件は `!` で否定しているので、パターン変数 `s` が参照できるのは条件が偽となる `if` の外になる

## Pattern Matching for instanceof が書ける場所
例では if の条件式で書いていたが、これ自体は最終的に boolean として評価される式なので、式を書ける場所なら書ける。

```java
    private static void patternMatch(Object obj) {
        String text = (obj instanceof String s) ? s.toUpperCase() : "DEFAULT";
        boolean b = (obj instanceof Exception e);
    }
```

- 1つ目は三項演算子の条件のところに、2つ目は boolean 変数の初期化に Pattern Matching for instanceof を記述している
    - 2つ目はパターン変数を使っていないのでこう書く意味は全く無い
    - あくまで書こうと思えば書けることを確認しているだけ

# Pattern Matching for switch
## Hello World
```java
public class PatternMatchForInstanceof {

    public static void main(String[] args) {
        System.out.println(patternMatch("string"));
        System.out.println(patternMatch(new Exception("exception")));
        System.out.println(patternMatch(10));
    }

    private static String patternMatch(Object obj) {
        return switch (obj) {
            case String s -> s.toUpperCase();
            case Exception e -> e.getMessage();
            default -> "DEFAULT(" + obj + ")";
        };
    }
}
```

```text:実行結果
STRING
exception
DEFAULT(10)
```

### 説明
```java
    private static String patternMatch(Object obj) {
        return switch (obj) {
            case String s -> s.toUpperCase();
            case Exception e -> e.getMessage();
            default -> "DEFAULT(" + obj + ")";
        };
    }
```

- switch のラベル部分でパターンマッチを記述できる
- `case 型 変数` と書くことで、 switch に渡した値の型でケースを分けることができる
- 型が一致した場合は変数に代入され、そのケースの中だけで利用できるようになる

## when句で条件を絞る
```java
public class PatternMatchForInstanceof {

    public static void main(String[] args) {
        System.out.println(patternMatch("one"));
        System.out.println(patternMatch("two"));
        System.out.println(patternMatch("three"));
    }

    private static String patternMatch(Object obj) {
        return switch (obj) {
            case String s when s.length() < 4 -> s;
            case String s -> s.substring(0, 3) + "...";
            default -> "DEFAULT(" + obj + ")";
        };
    }
}
```

```text:実行結果
one
two
thr...
```

- `case 型 変数 when 条件式` のように記述することで、型以外の任意の条件を追加できる
- 上の実装の場合、 `obj` が `String` でかつ長さが4文字未満であれば最初のケースにマッチし、4文字以上の `String` は2つ目のケースにマッチする
- ちなみに、1つ目と2つ目のケースを逆にするとコンパイルエラーになる

```java:コンパイルエラー
    private static String patternMatch(Object obj) {
        return switch (obj) {
            case String s -> s.substring(0, 3) + "...";
            // Label is dominated by a preceding case label 'String s'
            case String s when s.length() < 4 -> s;
            default -> "DEFAULT(" + obj + ")";
        };
    }
```

- 1つ目の条件があることで2つ目の条件は常に除外されるようになるため、コンパイルエラーとなっている

## シールクラスのパターンマッチ
```java:MySealedClass
public sealed class MySealedClass permits Hoge, Fuga {
}
```

```java:Hoge
public final class Hoge extends MySealedClass {
}
```

```java:Fuga
public final class Fuga extends MySealedClass {
}
```

```plantuml
class MySealedClass <<sealed>>
note right
permits Hoge, Fuga
end note

class Hoge <<final>> extends MySealedClass
class Fuga <<final>> extends MySealedClass
```

```java
public class SwitchExpressionTest {

    public static void main(String[] args) {
        System.out.println(patternMatch(new MySealedClass()));
        System.out.println(patternMatch(new Hoge()));
        System.out.println(patternMatch(new Fuga()));
    }

    private static String patternMatch(MySealedClass msc) {
        return switch (msc) {
            case Hoge h -> "Hoge";
            case Fuga f -> "Fuga";
            case MySealedClass m -> "MySealedClass";
        };
    }
}
```

```text:実行結果
MySealedClass
Hoge
Fuga
```

- シールクラスを switch 式に渡してパターンマッチをした場合、可能性のある型を全て網羅できる形でケースを列挙すれば `default` ケースが不要となる
- 網羅できていないと、コンパイルエラーになる

```java:コンパイルエラー
    private static String patternMatch(MySealedClass msc) {
        return switch (msc) {
            case Hoge h -> "Hoge";
            case Fuga f -> "Fuga";
            // MySealedClass が来た場合が網羅されていないのでコンパイルエラー
        };
    }
```

```java:コンパイルOK
    private static String patternMatch(MySealedClass msc) {
        return switch (msc) {
            // Hoge, Fuga が来た場合も MySealedClass として処理されるので網羅できている
            case MySealedClass m -> "MySealedClass";
        };
    }
```


### non-sealed が含まれる場合
```java:MySealedClass
public sealed class MySealedClass permits Hoge, Fuga {
}
```

```java:Hoge
public final class Hoge extends MySealedClass {
}
```

```java:Fuga
public non-sealed class Fuga extends MySealedClass {
}
```

```java:Piyo
public class Piyo extends Fuga {
}
```

```plantuml
class MySealedClass <<sealed>>
note right
permits Hoge, Fuga
end note

class Hoge <<final>> extends MySealedClass
class Fuga <<non-sealed>> extends MySealedClass
class Piyo extends Fuga
```

- `Fuga` を `non-sealed` にして `Piyo` をサブクラスとして追加している
- この状態にしても、先ほどのパターンマッチの実装はコンパイルが通る

```java:コンパイルは通る
public class PatternMatchForInstanceof {

    public static void main(String[] args) {
        System.out.println(patternMatch(new MySealedClass()));
        System.out.println(patternMatch(new Hoge()));
        System.out.println(patternMatch(new Fuga()));
        System.out.println(patternMatch(new Piyo())); // ★Piyo を渡すケースを追加
    }

    private static String patternMatch(MySealedClass msc) {
        return switch (msc) {
            case Hoge h -> "Hoge";
            case Fuga f -> "Fuga";
            case MySealedClass m -> "MySealedClass";
        };
    }
}
```

```text:実行結果
MySealedClass
Hoge
Fuga
Fuga
```

- `Piyo` は `Fuga` のサブタイプなので、 `case Fuga f` のパターンにマッチして処理されている
- ちなみに、 `Piyo` のパターンのラベルを追加することもできる

```java
public class PatternMatchForInstanceof {

    public static void main(String[] args) {
        System.out.println(patternMatch(new MySealedClass()));
        System.out.println(patternMatch(new Hoge()));
        System.out.println(patternMatch(new Fuga()));
        System.out.println(patternMatch(new Piyo()));
    }

    private static String patternMatch(MySealedClass msc) {
        return switch (msc) {
            case Hoge h -> "Hoge";
            case Piyo p -> "Piyo"; // ★Piyo のラベルを追加
            case Fuga f -> "Fuga";
            case MySealedClass m -> "MySealedClass";
        };
    }
}
```

```text:実行結果
MySealedClass
Hoge
Fuga
Piyo
```

# レコードパターン
## Hello World
```java:MyRecord
public record MyRecord(int i, String s) {
    @Override
    public int i() {
        System.out.println("i()");
        return i;
    }

    @Override
    public String s() {
        System.out.println("s()");
        return s;
    }
}
```

```java
public class RecordPatternTest {
    public static void main(String[] args) {
        MyRecord record = new MyRecord(1, "test");

        System.out.println(recordPattern(record));
    }

    private static String recordPattern(Object obj) {
        if (obj instanceof MyRecord(int i, String s)) {
            return "i=" + i + ", s=" + s;
        } else {
            return "default";
        }
    }
}
```

```text:実行結果
i()
s()
i=1, s=test
```

### 説明
```java
    private static String recordPattern(Object obj) {
        if (obj instanceof MyRecord(int i, String s)) {
            return "i=" + i + ", s=" + s;
        } else {
            return "default";
        }
    }
```

- `instanceof` でレコードの型を判定したときに、レコードのコンポーネントを分解して抽出できる
- レコードの型名の後ろに `()` を付けて抽出するコンポーネントを列挙することで抽出できる（これを**パターンリスト**と呼ぶ）
    - 各パターンは、型名と変数名のセットで宣言する
- コンポーネントの値は、アクセサを経由して取得される
- パターンリストには、レコードクラスに定義されているコンポーネントを同じ順序で全て列挙する必要がある
    - 一致しない場合はコンパイルエラー
- 抽出したコンポーネントは、 `instanceof` が真と判定される範囲内で参照できるようになる

## 型推論
```java
    private static String recordPattern(Object obj) {
        if (obj instanceof MyRecord(var i, var s)) {
            return "i=" + i + ", s=" + s;
        } else {
            return "default";
        }
    }
```

- 各パターンの型には `var` が利用できる
- これにより、型の記述を省略できる
- レコードクラスの構造は明確に定義されているので、ここで `var` を使うのはアリなのかなと個人的には思う
  - なんの型の値が入っているかぱっと見で分からない場所で `var` を使うのは危険だと思う
  - しかし、ここはレコードクラスのコンポーネントであることがハッキリしているので、型は人間にも推測できると思う（私見）

## レコードパターンのネスト
```java:Hoge
public record HogeRecord(double d, FugaRecord fuga) {
}
```

```java:Fuga
public record FugaRecord(int i, String s) {
}
```

```plantuml
class HogeRecord <<record>> {
    d: double
}

class FugaRecord <<record>> {
    i: int
    s: String
}
HogeRecord --> "fuga" FugaRecord
```

```java
public class RecordPatternTest {
    public static void main(String[] args) {
        FugaRecord fuga = new FugaRecord(9, "test");
        HogeRecord hoge = new HogeRecord(1.1, fuga);

        System.out.println(recordPattern(hoge));
    }

    private static String recordPattern(Object obj) {
        if (obj instanceof HogeRecord(double d, FugaRecord(int i, String s))) {
            return "d=" + d + ", i=" + i + ", s=" + s;
        } else {
            return "default";
        }
    }
}
```

```text:実行結果
d=1.1, i=9, s=test
```

- レコードパターンのパターンリストは、パターンに別のレコードを含めることでネストさせることができる
- やりすぎると複雑になって可読性が落ちる気がする（私見）

# テキストブロック
## Hello World

```java
public class TextBlockTest {
    public static void main(String[] args) {
        String text = """
                Hello
                World""";

        System.out.println("=======================");
        System.out.println(text);
        System.out.println("=======================");
    }
}
```

```text:実行結果
=======================
Hello
World
=======================
```

### 説明
```java
        String text = """
                Hello
                World""";
```

- テキストブロックは、ダブルクォーテーション3つで文字列を囲うことで宣言できる
- 最初のダブルクォーテーション3つの後は改行が入る必要がある
    - `"""Hello` のようにダブルクォーテーション3つの直後から文字列を開始することはできない
- テキストブロック内の文字列は、改行も含めそのままの形で `String` の値になる
    - 先頭の改行は含まれない
    - 改行コードは Line Feed (`\n`)になる
        - ソースコードの改行コードが `\r\n` になっていても、コンパイラによって `\n` に正規化される
    - インデントの扱いについては後述

## エスケープ文字
```java
public class TextBlockTest {
    public static void main(String[] args) {
        String text = """
                "double quotation"
                line\nfeed""";

        System.out.println("text = \"" + text + "\"");
    }
}
```

```text:実行結果
=======================
"double quotation"
line
feed
=======================
```

- 単一のダブルクォーテーションはエスケープ無しで記述できる
- エスケープシーケンスは従来の文字列リテラルと同様に記述できる

## 末尾に改行を入れる
```java
public class TextBlockTest {
    public static void main(String[] args) throws Exception {
        String text = """
                Hello
                World
                """;

        System.out.println("=======================");
        System.out.println(text);
        System.out.println("=======================");
    }
}
```

```text:実行結果
=======================
Hello
World

=======================
```

- 末尾に改行を入れたい場合は、終端のダブルクォーテーション3つを文字列の次の行に書けばいい

## 末尾の空白除去
```java
public class TextBlockTest {
    public static void main(String[] args) throws Exception {
        String text = """
                half width white space***
                full width white space□□□
                tab->->->
                unicode Space\u0020\u0020\u0020
                unicode No-Break Space   
                unicode En Space\u2002\u2002\u2002
                unicode Thin Space\u2009\u2009\u2009
                unicode Hair Space\u200A\u200A\u200A
                unicode Idepgraphic Space\u3000\u3000\u3000""";

        System.out.println("=======================");
        System.out.println(text);
        System.out.println("=======================");
    }
}
```

- 上記コードは、分かりやすくするために以下の文字を置換して記載している
    - 半角スペース ` ` -> `*`
    - 全角スペース `　` -> `□`
    - タブ `	` -> `->`

```text:実行結果
=======================
half width white space
full width white space
tab
unicode Space
unicode No-Break Space???
unicode En Space
unicode Thin Space
unicode Hair Space
unicode Idepgraphic Space
=======================
```

- テキストブロック内の各行の末尾に存在するスペースは自動的に除去される
- 全角やタブ、それにユニコードでスペース扱いになっているものもだいたい除去される(No-Break Space は除去されなかったので、全部ではなさそう)
    - まぁ、普段使うスペースは基本除去されるものと思っておいた方がよさそう
- これをする理由は、テキストブロックの見た目と結果が常に一致するようにした方が間違いがなくて良いかららしい
    - 末尾のスペースを自動的に削除するようなエディタがあると、気づかぬうちに見た目と内容が変わるようなことがあるとかなんとか（ガイドにそんな感じのことが書かれていた）
    - >  This is done so that the contents of the text block are always visually discernible. If this were not done, a text editor that automatically strips trailing white space could invisibly change the contents of a text block.
    - https://docs.oracle.com/javase/jp/20/text-blocks/index.html#trailing-white-space
- 末尾の空白を自動除去してほしくないときの方法がガイドに書かれているが、いずれもやや泥臭い方法になる

```java
public class TextBlockTest {
    public static void main(String[] args) throws Exception {
        String s = """
                Hello$$$
                World""".replace("$", " ");

        String t = """
                Hello   |
                World""".replace("|\n", "\n");

        String u = """
                Hello  \040
                World""";

        String v = """
                Hello  \s
                World""";

        System.out.println("=======================");
        System.out.println(s);
        System.out.println("=======================");
        System.out.println(t);
        System.out.println("=======================");
        System.out.println(u);
        System.out.println("=======================");
    }
}
```

```text:実行結果
=======================
Hello***
World
=======================
Hello***
World
=======================
Hello***
World
=======================
Hello***
World
=======================
```

- 実行結果の方は、分かりやすくするために半角スペースを `*` に置換している
- 末尾の空白を除去されたくない場合は、以下のいずれかの方法で対応できる
    - 文字列置換を使って後でスペースに置き換える
    - スペースの末尾に8進数表現のスペース(`\040`)かスペースのエスケープシーケンス (`\s`) を置く
        - これらはコンパイラによる末尾の空白の除外の後でスペースに置き換えられるので、除去を回避できるらしい
- 個人的には、最後の「末尾に `\s` を置く」のが一番マシかなと思う（私見）

## 改行をエスケープする
```java
public class TextBlockTest {
    public static void main(String[] args) throws Exception {
        String text = """
              Hello \
              World""";

        System.out.println("=======================");
        System.out.println(text);
        System.out.println("=======================");
    }
}
```

```text:実行結果
=======================
Hello World
=======================
```

- 各行の末尾の改行の手前に `\` を置くことで、その改行をエスケープできる


## インデント
```java
public class TextBlockTest {
    public static void main(String[] args) {
        String text = """
****************Hoge
************
**************Fuga
******************Piyo""";

        System.out.println("=======================");
        System.out.println(text);
        System.out.println("=======================");
    }
}
```

```text:実行結果
=======================
**Hoge

Fuga
****Piyo
=======================
```

- 分かりやすくするために半角スペースを `*` に置換して表現している（実際はただの空白スペース）
- テキストブロック内の各行の先頭のスペースは、余計な分が自動的に除去される
- インデントが最も小さい行が基準となり、その行のインデントサイズが余計な分として判断される
    - `Fuga` の行が最もインデントが小さいので、その行のインデントが基準となり他の行の先頭のスペースも除去されている
    - 空行(空白だけの行も含む)はインデントの基準の判定からは除外されている
- インデントが最も小さい行よりも手前の空白も残したい場合は、以下のようにする

```java
public class TextBlockTest {
    public static void main(String[] args) throws Exception {
        String text = """
                Hoge
            
              Fuga
                  Piyo
            """;

        System.out.println("=======================");
        System.out.println(text);
        System.out.println("=======================");
    }
}
```

```text:実行結果
=======================
    Hoge

  Fuga
      Piyo

=======================
```

- 末尾のダブルクォーテーションを改行させ、先頭のスペースを維持したい高さに配置する
- これにより、基準となるインデントの高さは末尾のダブルクォーテーションの位置となる
- ただし、この場合は末尾に必ず改行コードが入ることになる
- もし末尾のダブルクォーテーションでインデントを調整しつつ改行コードを最後に入れたくない場合は、次のようにする

```java
    public static void main(String[] args) throws Exception {
        String text = """
                Hoge
            
              Fuga
                  Piyo\
            """;

        System.out.println("=======================");
        System.out.println(text);
        System.out.println("=======================");
    }
```

```text:実行結果
=======================
    Hoge

  Fuga
      Piyo
=======================
```

- 最後の改行の前に `\` を入れることで、最後の改行をエスケープできる


# 参考
- [レコード・クラス](https://docs.oracle.com/javase/jp/16/language/records.html#GUID-6699E26F-4A9B-4393-A08B-1E47D4B2D263)
- [Switch式](https://docs.oracle.com/javase/jp/16/language/switch-expressions.html#GUID-BA4F63E3-4823-43C6-A5F3-BAA4A2EF3ADC)
- [シール・クラス](https://docs.oracle.com/javase/jp/17/language/sealed-classes-and-interfaces.html#GUID-0C709461-CC33-419A-82BF-61461336E65F)
- [レコード・パターン](https://docs.oracle.com/javase/jp/20/language/record-patterns.html#GUID-7623D3AD-4141-4914-A384-60C65BD0C010)
- [Record Patterns](https://docs.oracle.com/en/java/javase/21/language/record-patterns.html#GUID-7623D3AD-4141-4914-A384-60C65BD0C010)
- [Java 21新機能まとめ #Java - Qiita](https://qiita.com/nowokay/items/174f75b9e48cc7a80838#440-record-patterns)
- [Unicode のスペースは色々あるし、半角スペースと &nbsp は同じでもない - A Memorandum](https://blog1.mammb.com/entry/2021/11/11/090000)
- [Programmer's Guide to Text Blocks](https://docs.oracle.com/en/java/javase/21/text-blocks/index.html)