Skip to content
Published on

モダンJava完全構文ガイド:Java 17/21エディション

Authors

モダンJava完全構文ガイド:Java 17/21エディション

Javaは着実に進化しています。Java 21 LTSには、Project Loom(仮想スレッド)、パターンマッチング、Record、Sealed Classesなど革新的な機能が含まれています。本ガイドでは、現場で必ず知っておくべきモダンJavaの構文を完全に網羅します。


1. Java 21 コア新機能(LTS基準)

1.1 Recordクラス(Java 16+)

Recordはイミュータブル(不変)データクラスを簡潔に宣言する方法です。コンパイラがコンストラクタ、getter、equals、hashCode、toStringを自動生成します。

// 旧来のアプローチ(ボイラープレートだらけ)
public class PersonOld {
    private final String name;
    private final int age;

    public PersonOld(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() { return name; }
    public int getAge() { return age; }

    @Override public boolean equals(Object o) { /* ... */ }
    @Override public int hashCode() { /* ... */ }
    @Override public String toString() { /* ... */ }
}

// Recordアプローチ(1行!)
public record Person(String name, int age) {}

// 使用方法
Person p = new Person("Alice", 30);
System.out.println(p.name()); // "Alice"(getterの名前はフィールド名と同じ)
System.out.println(p);        // Person[name=Alice, age=30]

// カスタムコンストラクタでバリデーション
public record Person(String name, int age) {
    public Person {
        if (name == null || name.isBlank()) throw new IllegalArgumentException("nameを空にできません");
        if (age < 0 || age > 150) throw new IllegalArgumentException("無効な年齢: " + age);
    }

    // 追加メソッドの定義が可能
    public String greeting() {
        return "こんにちは、私は" + name + "、" + age + "歳です。";
    }
}

// Recordはインターフェースを実装可能
public record Point(double x, double y) implements Comparable<Point> {
    @Override
    public int compareTo(Point other) {
        return Double.compare(this.distance(), other.distance());
    }

    private double distance() {
        return Math.sqrt(x * x + y * y);
    }
}

Recordの制約:

  • 継承不可(暗黙的にfinal)
  • フィールドはすべてprivate final
  • インスタンスフィールドの追加不可(staticフィールドは可能)

1.2 Sealed Classes(Java 17+)

Sealed Classは、どのクラスやインターフェースが特定の型を継承・実装できるかを明示的に制限します。これにより、型階層を閉じた設計にできます。

// ShapeはCircle、Rectangle、Triangleのみが継承可能
public sealed class Shape permits Circle, Rectangle, Triangle {}

public final class Circle extends Shape {
    private final double radius;
    public Circle(double radius) { this.radius = radius; }
    public double radius() { return radius; }
}

public final class Rectangle extends Shape {
    private final double width, height;
    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }
    public double width() { return width; }
    public double height() { return height; }
}

public non-sealed class Triangle extends Shape {
    // non-sealed:Triangleを継承するクラスは制限なし
    private final double base, height;
    public Triangle(double base, double height) {
        this.base = base;
        this.height = height;
    }
}

// インターフェースにもsealed適用可能
public sealed interface Result<T> permits Result.Success, Result.Failure {
    record Success<T>(T value) implements Result<T> {}
    record Failure<T>(String error) implements Result<T> {}
}

1.3 instanceofのパターンマッチング(Java 16+)

instanceof後に明示的なキャストなしで直接使用できます。

// 旧来のアプローチ
if (obj instanceof String) {
    String s = (String) obj;
    System.out.println(s.length());
}

// モダンアプローチ(パターンマッチング)
if (obj instanceof String s) {
    System.out.println(s.length()); // sはStringとして自動キャスト
}

// 条件と組み合わせ
if (obj instanceof String s && s.length() > 5) {
    System.out.println("長い文字列: " + s);
}

// 否定パターン
if (!(obj instanceof String s)) {
    System.out.println("文字列ではありません");
    return;
}
// ここからsが使用可能
System.out.println(s.toUpperCase());

1.4 switchのパターンマッチング(Java 21)

switch式でタイプパターンマッチングをサポートします。Sealed Classと組み合わせると強力です。

// タイプベースのswitch
public double calculateArea(Shape shape) {
    return switch (shape) {
        case Circle c -> Math.PI * c.radius() * c.radius();
        case Rectangle r -> r.width() * r.height();
        case Triangle t -> 0.5 * t.base() * t.height();
        // sealedクラスなのでdefault不要(全ケースカバー済み)
    };
}

// ガードパターン(条件付きパターン)
public String classify(Object obj) {
    return switch (obj) {
        case Integer i when i < 0 -> "負の整数: " + i;
        case Integer i when i == 0 -> "ゼロ";
        case Integer i -> "正の整数: " + i;
        case String s when s.isEmpty() -> "空文字列";
        case String s -> "文字列: " + s;
        case null -> "null値";
        default -> "その他: " + obj.getClass().getSimpleName();
    };
}

// Recordパターンの分解
public String describePoint(Object obj) {
    return switch (obj) {
        case Point(double x, double y) when x == 0 && y == 0 -> "原点";
        case Point(double x, double y) when x == 0 -> "Y軸上の " + y;
        case Point(double x, double y) when y == 0 -> "X軸上の " + x;
        case Point(double x, double y) -> "座標 (" + x + ", " + y + ")";
        default -> "Pointではありません";
    };
}

1.5 テキストブロック(Java 15+)

複数行文字列を読みやすく記述します。

// 旧来のアプローチ
String json = "{\n" +
    "  \"name\": \"Alice\",\n" +
    "  \"age\": 30\n" +
    "}";

// テキストブロック
String json = """
        {
          "name": "Alice",
          "age": 30
        }
        """;

// SQLの例
String sql = """
        SELECT u.id, u.name, o.total
        FROM users u
        JOIN orders o ON u.id = o.user_id
        WHERE u.active = true
        ORDER BY o.total DESC
        """;

// エスケープシーケンス: \s(末尾空白を保持)、\ (改行を抑制)
String oneLine = """
        Hello \
        World\
        """; // "Hello World"

1.6 仮想スレッド(Java 21、Project Loom)

仮想スレッドはJVMが管理する軽量スレッドです。OSスレッドとの1:1マッピングの代わりにM:N方式を採用し、数百万の同時タスクが可能です。

// 従来のプラットフォームスレッド
Thread platformThread = new Thread(() -> {
    System.out.println("プラットフォームスレッド: " + Thread.currentThread());
});
platformThread.start();

// 仮想スレッドの作成
Thread virtualThread = Thread.ofVirtual()
    .name("my-virtual-thread")
    .start(() -> {
        System.out.println("仮想スレッド: " + Thread.currentThread());
    });

// ExecutorServiceで仮想スレッドを使用
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
    List<Future<String>> futures = new ArrayList<>();
    for (int i = 0; i < 10_000; i++) {
        int taskId = i;
        futures.add(executor.submit(() -> {
            // ブロッキングI/Oも問題なし - 仮想スレッドはブロック時にアンマウント
            Thread.sleep(100);
            return "タスク " + taskId + " 完了";
        }));
    }
    for (Future<String> f : futures) {
        System.out.println(f.get());
    }
}

// Spring Bootとの連携(application.properties)
// spring.threads.virtual.enabled=true

// 構造化並行処理(プレビュー機能)
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    Future<User> user = scope.fork(() -> fetchUser(userId));
    Future<List<Order>> orders = scope.fork(() -> fetchOrders(userId));
    scope.join().throwIfFailed();
    return new UserProfile(user.resultNow(), orders.resultNow());
}

1.7 シーケンスコレクション(Java 21)

コレクションに順序の概念を追加する新しいインターフェースです。

// SequencedCollection: getFirst()、getLast()、addFirst()、addLast()、reversed()
List<String> list = new ArrayList<>(List.of("a", "b", "c"));
System.out.println(list.getFirst()); // "a"
System.out.println(list.getLast());  // "c"
list.addFirst("z");                  // ["z", "a", "b", "c"]
list.addLast("x");                   // ["z", "a", "b", "c", "x"]
List<String> reversed = list.reversed(); // ["x", "c", "b", "a", "z"]

// SequencedMap: firstEntry()、lastEntry()、putFirst()、putLast()、reversed()
LinkedHashMap<String, Integer> map = new LinkedHashMap<>();
map.put("one", 1);
map.put("two", 2);
map.put("three", 3);
System.out.println(map.firstEntry()); // one=1
System.out.println(map.lastEntry());  // three=3

2. ラムダ式とStream API

2.1 ラムダ式の構文

// 基本形式
Runnable r = () -> System.out.println("こんにちは");
Comparator<String> comp = (a, b) -> a.compareTo(b);

// ブロックラムダ(複数行)
Function<Integer, Integer> factorial = n -> {
    int result = 1;
    for (int i = 1; i <= n; i++) result *= i;
    return result;
};

// 型推論
List<String> names = List.of("Charlie", "Alice", "Bob");
names.sort((a, b) -> a.compareTo(b));
// またはメソッド参照
names.sort(String::compareTo);

2.2 関数型インターフェース

// Function<T, R>: T -> R
Function<String, Integer> strLen = String::length;
Function<Integer, Integer> doubler = x -> x * 2;
Function<String, Integer> lenThenDouble = strLen.andThen(doubler);
System.out.println(lenThenDouble.apply("Hello")); // 10

// BiFunction<T, U, R>: T, U -> R
BiFunction<String, Integer, String> repeat = (s, n) -> s.repeat(n);
System.out.println(repeat.apply("Java", 3)); // "JavaJavaJava"

// Predicate<T>: T -> boolean
Predicate<String> isLong = s -> s.length() > 5;
Predicate<String> startsWithA = s -> s.startsWith("A");
Predicate<String> combined = isLong.and(startsWithA);
Predicate<String> either = isLong.or(startsWithA);
Predicate<String> notLong = isLong.negate();

// Supplier<T>: () -> T
Supplier<LocalDate> today = LocalDate::now;
Supplier<List<String>> listFactory = ArrayList::new;

// Consumer<T>: T -> void
Consumer<String> printer = System.out::println;
Consumer<String> both = printer.andThen(s -> log.info("処理中: {}", s));

// UnaryOperator<T>: T -> T(Functionの特殊化)
UnaryOperator<String> toUpper = String::toUpperCase;

// BinaryOperator<T>: T, T -> T
BinaryOperator<Integer> add = Integer::sum;

2.3 メソッド参照(4種類)

// 1. 静的メソッド参照: ClassName::staticMethod
Function<String, Integer> parseInt = Integer::parseInt;

// 2. 特定インスタンスのメソッド参照: instance::instanceMethod
String prefix = "こんにちは、";
Function<String, String> greeter = prefix::concat;
System.out.println(greeter.apply("世界")); // "こんにちは、世界"

// 3. 任意インスタンスのメソッド参照: ClassName::instanceMethod
Function<String, String> upper = String::toUpperCase;
Comparator<String> byLength = Comparator.comparingInt(String::length);

// 4. コンストラクタ参照: ClassName::new
Supplier<ArrayList<String>> listCreator = ArrayList::new;
Function<String, StringBuilder> sbCreator = StringBuilder::new;

2.4 Stream APIのコア操作

List<Employee> employees = List.of(
    new Employee("Alice", "Engineering", 90_000),
    new Employee("Bob", "Engineering", 80_000),
    new Employee("Charlie", "Marketing", 60_000),
    new Employee("Diana", "Marketing", 70_000),
    new Employee("Eve", "Engineering", 95_000)
);

// filter + map + collect
List<String> highEarnerNames = employees.stream()
    .filter(e -> e.salary() > 75_000)
    .map(Employee::name)
    .sorted()
    .toList(); // Java 16+(変更不可リスト)

// flatMap: Stream<Stream<T>> -> Stream<T>
List<List<Integer>> nested = List.of(List.of(1, 2, 3), List.of(4, 5), List.of(6));
List<Integer> flat = nested.stream()
    .flatMap(Collection::stream)
    .toList();
// [1, 2, 3, 4, 5, 6]

// groupingBy
Map<String, List<Employee>> byDept = employees.stream()
    .collect(Collectors.groupingBy(Employee::department));

Map<String, Double> avgSalaryByDept = employees.stream()
    .collect(Collectors.groupingBy(
        Employee::department,
        Collectors.averagingInt(Employee::salary)
    ));

// partitioningBy
Map<Boolean, List<Employee>> partition = employees.stream()
    .collect(Collectors.partitioningBy(e -> e.salary() > 75_000));
// true -> 高給者、false -> その他

// joining
String names = employees.stream()
    .map(Employee::name)
    .collect(Collectors.joining(", ", "[", "]"));
// "[Alice, Bob, Charlie, Diana, Eve]"

// toMap
Map<String, Integer> nameSalaryMap = employees.stream()
    .collect(Collectors.toMap(Employee::name, Employee::salary));

2.5 Optional

// Optionalの作成
Optional<String> opt1 = Optional.of("value");       // null不可
Optional<String> opt2 = Optional.ofNullable(null);  // null許容 -> empty
Optional<String> opt3 = Optional.empty();

// 値の取り出し
String val1 = opt1.get();                              // NoSuchElementExceptionの危険
String val2 = opt1.orElse("デフォルト");              // デフォルト値(常に評価)
String val3 = opt1.orElseGet(() -> computeDefault()); // 遅延評価
String val4 = opt1.orElseThrow(() -> new NotFoundException("見つかりません"));

// 変換
Optional<Integer> length = opt1.map(String::length);

// flatMap: Optional<Optional<T>>を防ぐ
Optional<Address> address = Optional.of(user)
    .flatMap(u -> Optional.ofNullable(u.getAddress()));

// 条件付き処理
opt1.ifPresent(System.out::println);
opt1.ifPresentOrElse(
    v -> System.out.println("見つかった: " + v),
    () -> System.out.println("見つかりません")
);

// アンチパターン(避けるべき):
// - isPresent()チェックなしのopt.get()
// - メソッドパラメータとしてのOptional使用
// - ドメインエンティティのgetterでのOptional(戻り値にのみ使用推奨)

3. ジェネリクス深堀り

3.1 ワイルドカードとPECS原則

PECS: Producer Extends、Consumer Super

// 共変(extends)— 読み取り(Producer)用
public double sumList(List<? extends Number> list) {
    double sum = 0;
    for (Number n : list) sum += n.doubleValue(); // 読み取りOK
    // list.add(1.0); // コンパイルエラー — 書き込み不可
    return sum;
}
// List<Integer>、List<Double>などで呼び出し可能

// 反変(super)— 書き込み(Consumer)用
public void addNumbers(List<? super Integer> list) {
    list.add(1);
    list.add(2); // 書き込みOK
    // Integer n = list.get(0); // コンパイルエラー — 型が保証されない
    Object obj = list.get(0); // OK
}
// List<Integer>、List<Number>、List<Object>で呼び出し可能

// コピーメソッド(PECS完全適用)
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
    for (T item : src) {  // srcはProducer -> extends
        dest.add(item);   // destはConsumer -> super
    }
}

3.2 ジェネリクスメソッドと型消去

// ジェネリクスメソッド
public static <T extends Comparable<T>> T max(T a, T b) {
    return a.compareTo(b) >= 0 ? a : b;
}

// 再帰型境界
public static <T extends Comparable<T>> T findMax(List<T> list) {
    return list.stream().max(Comparator.naturalOrder()).orElseThrow();
}

// 型消去(Type Erasure)
// コンパイル後、List<String>はListになる(実行時に型情報なし)
List<String> strings = new ArrayList<>();
List<Integer> integers = new ArrayList<>();
System.out.println(strings.getClass() == integers.getClass()); // true!

// instanceofにジェネリクス型は使用不可
// if (obj instanceof List<String>) {} // コンパイルエラー

// 正しいアプローチ
if (obj instanceof List<?> list && !list.isEmpty() && list.get(0) instanceof String) {
    @SuppressWarnings("unchecked")
    List<String> stringList = (List<String>) list;
}

// ジェネリクス配列の作成不可
// T[] arr = new T[10]; // コンパイルエラー
// 回避策:
@SuppressWarnings("unchecked")
T[] arr = (T[]) new Object[10];

4. コレクションフレームワーク

4.1 不変コレクション(Java 9+)

// 不変List(null不可、順序保持)
List<String> immutableList = List.of("a", "b", "c");
// immutableList.add("d"); // UnsupportedOperationException

// 不変Set
Set<Integer> immutableSet = Set.of(1, 2, 3, 4, 5);
// Set.ofは順序保証なし、重複不可

// 不変Map
Map<String, Integer> immutableMap = Map.of(
    "one", 1,
    "two", 2,
    "three", 3
);

// 10エントリ以上: Map.ofEntries
Map<String, Integer> bigMap = Map.ofEntries(
    Map.entry("a", 1),
    Map.entry("b", 2),
    Map.entry("c", 3)
);

// 不変コピー
List<String> original = new ArrayList<>(List.of("a", "b"));
List<String> copy = List.copyOf(original);
Map<String, Integer> mapCopy = Map.copyOf(immutableMap);

4.2 適切なMapの選択

// HashMap: 順序なし、平均O(1)
Map<String, Integer> hashMap = new HashMap<>();

// LinkedHashMap: 挿入順序保持、平均O(1)
Map<String, Integer> linkedMap = new LinkedHashMap<>();

// TreeMap: キーでソート、O(log n)
Map<String, Integer> treeMap = new TreeMap<>();
Map<String, Integer> reverseMap = new TreeMap<>(Comparator.reverseOrder());

// ConcurrentHashMap: スレッドセーフ、セグメントロック
ConcurrentHashMap<String, Integer> concurrentMap = new ConcurrentHashMap<>();
concurrentMap.computeIfAbsent("key", k -> 0);
concurrentMap.merge("key", 1, Integer::sum); // アトミックカウンター

// EnumMap: Enumキー、非常に高速
EnumMap<DayOfWeek, String> schedule = new EnumMap<>(DayOfWeek.class);

5. 例外処理パターン

5.1 チェック例外 vs 非チェック例外

// チェック例外: コンパイラが処理を強制
public String readFile(String path) throws IOException {
    return Files.readString(Path.of(path));
}

// 非チェック例外: RuntimeExceptionのサブクラス
public int divide(int a, int b) {
    if (b == 0) throw new ArithmeticException("ゼロ除算");
    return a / b;
}

// カスタム例外階層の設計
public class AppException extends RuntimeException {
    private final String errorCode;

    public AppException(String errorCode, String message) {
        super(message);
        this.errorCode = errorCode;
    }

    public AppException(String errorCode, String message, Throwable cause) {
        super(message, cause);
        this.errorCode = errorCode;
    }

    public String getErrorCode() { return errorCode; }
}

public class ResourceNotFoundException extends AppException {
    public ResourceNotFoundException(String resourceType, Object id) {
        super("RESOURCE_NOT_FOUND",
              resourceType + "が見つかりません。ID: " + id);
    }
}

5.2 try-with-resourcesとマルチキャッチ

// try-with-resources(AutoCloseable実装)
public String readAndClose(String path) throws IOException {
    try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
        return reader.lines().collect(Collectors.joining("\n"));
    } // reader.close()が自動呼び出し
}

// 複数リソース(逆順でクローズ)
public void copyFile(String src, String dest) throws IOException {
    try (
        InputStream in = new FileInputStream(src);
        OutputStream out = new FileOutputStream(dest)
    ) {
        in.transferTo(out);
    }
}

// マルチキャッチ
public void process(String input) {
    try {
        int value = Integer.parseInt(input);
        String result = riskyOperation(value);
        System.out.println(result);
    } catch (NumberFormatException | IllegalArgumentException e) {
        System.err.println("無効な入力: " + e.getMessage());
    } catch (RuntimeException e) {
        log.error("予期しないエラー", e);
        throw e;
    }
}

6. 並行処理(Concurrency)

6.1 CompletableFuture

// 非同期操作チェーン
CompletableFuture<String> future = CompletableFuture
    .supplyAsync(() -> fetchUserId())
    .thenApplyAsync(id -> fetchUser(id))
    .thenApply(user -> user.getName())
    .exceptionally(ex -> "不明なユーザー");

// thenCompose: 非同期のflatMap
CompletableFuture<Order> orderFuture = CompletableFuture
    .supplyAsync(() -> fetchUser(userId))
    .thenCompose(user -> CompletableFuture.supplyAsync(() -> fetchLatestOrder(user)));

// thenCombine: 2つの非同期結果を結合
CompletableFuture<User> userFuture = CompletableFuture.supplyAsync(() -> fetchUser(id));
CompletableFuture<List<Order>> ordersFuture = CompletableFuture.supplyAsync(() -> fetchOrders(id));

CompletableFuture<UserProfile> profileFuture = userFuture.thenCombine(
    ordersFuture,
    (user, orders) -> new UserProfile(user, orders)
);

// allOf: すべて完了まで待機
CompletableFuture<Void> allDone = CompletableFuture.allOf(task1, task2, task3);

// anyOf: 最初に完了したものを取得
CompletableFuture<Object> firstDone = CompletableFuture.anyOf(task1, task2, task3);

// タイムアウト
CompletableFuture<String> withTimeout = future
    .orTimeout(5, TimeUnit.SECONDS)
    .completeOnTimeout("デフォルト", 3, TimeUnit.SECONDS);

6.2 ロック

// ReentrantLock
ReentrantLock lock = new ReentrantLock(true); // フェアロック
lock.lock();
try {
    // クリティカルセクション
} finally {
    lock.unlock(); // finallyで必ず解放
}

// tryLockでタイムアウト
if (lock.tryLock(1, TimeUnit.SECONDS)) {
    try {
        // クリティカルセクション
    } finally {
        lock.unlock();
    }
}

// ReadWriteLock: 多数の読み取り、単一の書き込み
ReadWriteLock rwLock = new ReentrantReadWriteLock();
Map<String, String> cache = new HashMap<>();

public String get(String key) {
    rwLock.readLock().lock();
    try { return cache.get(key); }
    finally { rwLock.readLock().unlock(); }
}

public void put(String key, String value) {
    rwLock.writeLock().lock();
    try { cache.put(key, value); }
    finally { rwLock.writeLock().unlock(); }
}

7. モダンJavaパターン

7.1 Builderパターン

public class HttpRequest {
    private final String url;
    private final String method;
    private final Map<String, String> headers;
    private final String body;
    private final int timeoutMs;

    private HttpRequest(Builder builder) {
        this.url = builder.url;
        this.method = builder.method;
        this.headers = Map.copyOf(builder.headers);
        this.body = builder.body;
        this.timeoutMs = builder.timeoutMs;
    }

    public static Builder builder(String url) {
        return new Builder(url);
    }

    public static class Builder {
        private final String url;
        private String method = "GET";
        private Map<String, String> headers = new HashMap<>();
        private String body;
        private int timeoutMs = 5000;

        private Builder(String url) { this.url = url; }

        public Builder method(String method) { this.method = method; return this; }
        public Builder header(String key, String value) { headers.put(key, value); return this; }
        public Builder body(String body) { this.body = body; return this; }
        public Builder timeout(int ms) { this.timeoutMs = ms; return this; }
        public HttpRequest build() { return new HttpRequest(this); }
    }
}

// 使用例
HttpRequest request = HttpRequest.builder("https://api.example.com/users")
    .method("POST")
    .header("Content-Type", "application/json")
    .header("Authorization", "Bearer token123")
    .body("{\"name\": \"Alice\"}")
    .timeout(3000)
    .build();

7.2 Sealed Class + パターンマッチングでADTを実現

// Resultタイプ(Eitherに類似)
public sealed interface Result<T> permits Result.Ok, Result.Err {

    record Ok<T>(T value) implements Result<T> {}
    record Err<T>(String message, Throwable cause) implements Result<T> {
        public Err(String message) { this(message, null); }
    }

    static <T> Result<T> ok(T value) { return new Ok<>(value); }
    static <T> Result<T> err(String message) { return new Err<>(message); }
    static <T> Result<T> of(Supplier<T> supplier) {
        try { return ok(supplier.get()); }
        catch (Exception e) { return err(e.getMessage()); }
    }

    default <U> Result<U> map(Function<T, U> mapper) {
        return switch (this) {
            case Ok<T> ok -> Result.ok(mapper.apply(ok.value()));
            case Err<T> err -> Result.err(err.message());
        };
    }

    default T getOrElse(T defaultValue) {
        return switch (this) {
            case Ok<T> ok -> ok.value();
            case Err<T> ignored -> defaultValue;
        };
    }
}

// CommandパターンとADT
public sealed interface Command permits Command.CreateUser, Command.DeleteUser, Command.UpdateEmail {
    record CreateUser(String name, String email) implements Command {}
    record DeleteUser(String userId) implements Command {}
    record UpdateEmail(String userId, String newEmail) implements Command {}
}

public void handle(Command command) {
    switch (command) {
        case Command.CreateUser(String name, String email) ->
            userService.create(name, email);
        case Command.DeleteUser(String userId) ->
            userService.delete(userId);
        case Command.UpdateEmail(String userId, String newEmail) ->
            userService.updateEmail(userId, newEmail);
    }
}

8. クイズ

クイズ1: Recordに関する説明で誤っているものはどれですか?

選択肢:

  • A) コンパイラがequals、hashCode、toStringを自動生成する
  • B) インスタンスフィールドを追加宣言できる
  • C) インターフェースを実装できる
  • D) 他のクラスを継承できない

正解: B

解説: Recordはヘッダーで定義したコンポーネント以外のインスタンスフィールドを追加宣言できません。staticフィールドは可能です。Recordは暗黙的にjava.lang.Recordを継承するため他のクラスを継承できませんが、インターフェースは実装できます。

クイズ2: PECS原則において、public void addToList(List<___> list, T element)の正しいワイルドカードは?

選択肢:

  • A) extends T
  • B) super T
  • C) ?
  • D) ワイルドカード不要

正解: B (super T)

解説: PECS(Producer Extends、Consumer Super)において、listはelementを受け取るConsumer役割のため? super Tを使用します。? extends Tは読み取り(Producer)に使います。List<? super T>はTまたはTのスーパータイプのリストを受け付け、T要素の安全な追加を可能にします。

クイズ3: 仮想スレッドに関する説明で正しいものはどれですか?

選択肢:

  • A) 仮想スレッドはOSスレッドと1:1でマッピングされる
  • B) CPU集約的な処理に特に効果的だ
  • C) ブロッキングI/O時にキャリアスレッドからアンマウントされる
  • D) synchronizedブロック内でも常に最適なパフォーマンスを発揮する

正解: C

解説: 仮想スレッドはブロッキングI/Oが発生するとOSスレッド(キャリアスレッド)からアンマウントされ、他の仮想スレッドがそのOSスレッドを使用できるようになります。CPU集約的な処理よりI/O集約的な処理に適しています。synchronizedブロックではピニングが発生する可能性があり(ReentrantLockを推奨)、パフォーマンスが低下することがあります。

クイズ4: 次のStreamコードの結果は何ですか?List.of(1, 2, 3, 4, 5).stream().filter(n -> n % 2 == 0).map(n -> n * n).reduce(0, Integer::sum)

選択肢:

  • A) 20
  • B) 29
  • C) 25
  • D) 4

正解: A (20)

解説: filterで偶数を抽出 [2, 4]、mapで二乗 [4, 16]、reduceで合計 4 + 16 = 20。

クイズ5: Sealedクラスのpermitsに列挙されたサブクラスとして宣言できないのは何ですか?

選択肢:

  • A) finalとして宣言(それ以上継承不可)
  • B) sealedとして宣言(自身のサブタイプを制限)
  • C) non-sealedとして宣言(自由に継承を許可)
  • D) abstractのみで宣言(sealed/non-sealed/finalなし)

正解: D

解説: Sealedクラスの直接サブクラスは必ずfinalsealed、またはnon-sealedのいずれかで宣言する必要があります。abstractのみでは要件を満たしません(abstractはsealedfinalと組み合わせることは可能ですが、単独では3つの必須修飾子の条件を満たしません)。Javaコンパイラはシールされた階層のすべてのサブクラスを把握している必要があるためです。


まとめ

モダンJavaは単純な構文改善を超えて、関数型プログラミング、型安全性、高性能な並行処理を言語レベルでサポートしています。Java 21 LTSをベースに:

  • Record + Sealed Class + パターンマッチング: 型安全なドメインモデル
  • Stream API + Optional: 宣言的なデータ処理
  • 仮想スレッド: 高スループットI/O並行処理
  • CompletableFuture: 非同期パイプライン

これらの機能を適切に組み合わせることで、簡潔かつ安全で高性能なJavaコードを書くことができます。