0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

こんにちは、もんすんです。

前回のJava10→11編では、LTSの意味から始まり、文字列メソッドの強化・標準HttpClient・単一ファイル実行などを見ていきました。

今回は Java11→17編です。

この回が、連載の中で最もボリューミーです。なぜかというと——Java12〜16は短命リリース(各6ヶ月)なのに、この5バージョン分の変化を1本にまとめているからです。

でも大丈夫です。詰め込みすぎずに「実務で効く項目」に絞っています。
(私が全部理解できていないということもあります)

「switch が式になった。テキストが複数行書ける。record がある。sealed がある——つまり、Javaが現代的になってきた。」

そういう回です。やっていきましょう。

対象読者

  • Java10→11編を読んだ方(または Java11 の概要を知っている方)
  • recordswitch式、テキストブロックを使ったことがない方

Java11からJava17へのレベルアップ

1. switch 式(Java14より)

従来の switch 文が、値を返す「式」 として書けるようになりました。

Before(Java 11 以前の switch 文)

String day = "MONDAY";
int numLetters;

switch (day) {
    case "MONDAY":
    case "FRIDAY":
    case "SUNDAY":
        numLetters = 6;
        break;
    case "TUESDAY":
        numLetters = 7;
        break;
    case "THURSDAY":
    case "SATURDAY":
        numLetters = 8;
        break;
    case "WEDNESDAY":
        numLetters = 9;
        break;
    default:
        throw new IllegalArgumentException("無効な曜日: " + day);
}

break を書き忘れると次の case に落ちる(フォールスルー)。冗長でバグの温床です。

After(Java 14 以降の switch 式)

String day = "MONDAY";
int numLetters = switch (day) {
    case "MONDAY", "FRIDAY", "SUNDAY" -> 6;
    case "TUESDAY"                    -> 7;
    case "THURSDAY", "SATURDAY"       -> 8;
    case "WEDNESDAY"                  -> 9;
    default -> throw new IllegalArgumentException("無効な曜日: " + day);
};
  • -> 構文break 不要
  • 複数の case をカンマでまとめられる
  • 全ケースを網羅していないとコンパイルエラー(安全)
  • 値を直接変数に代入できる

実務で switch を使うたびに「あっ、これ式で書けばよかった」となります。

ブロックが必要なときは yield

String result = switch (code) {
    case 200 -> "OK";
    case 404 -> "Not Found";
    default -> {
        var message = "Unknown: " + code;
        System.out.println(message);
        yield message;  // ブロック内で値を返すときは yield
    }
};

2. テキストブロック(Java 15 にて正式へ)

複数行の文字列を、見た目通りに書けるようになりました。

Before(Java 14 以前)

// JSON を文字列で持つとき
String json = "{\n" +
              "  \"name\": \"田中\",\n" +
              "  \"age\": 30\n" +
              "}";

// HTMLを文字列で持つとき
String html = "<html>\n" +
              "  <body>\n" +
              "    <p>こんにちは</p>\n" +
              "  </body>\n" +
              "</html>";

エスケープが多くて読みにくい上、閉じダブルクォートを忘れやすい。

After(Java 15 以降のテキストブロック)

// """ で始まり """ で終わる
String json = """
        {
          "name": "田中",
          "age": 30
        }
        """;

String html = """
        <html>
          <body>
            <p>こんにちは</p>
          </body>
        </html>
        """;
  • """ で囲むだけ、エスケープ不要
  • インデントは自動で除去(終端 """ の位置が基準)
  • SQL クエリや設定テンプレートにも重宝

私の実務では、テスト用の JSON やメール本文テンプレートを書くときに毎回助かっています。

3. record クラス(Java 16 から正式へ)

データを保持するだけのクラスを、極限まで簡潔に書くための構文です。

Before(Java 15 以前)

// 名前と年齢だけを持つクラス
public final class Person {
    private final String name;
    private final int age;

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

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

    @Override
    public boolean equals(Object obj) { ... }
    @Override
    public int hashCode() { ... }
    @Override
    public String toString() { ... }
}

これを全部書くのが Java の「儀式」でした(Lombokで省略している現場も多かった)。

After(Java 16 以降の record)

public record Person(String name, int age) {}

1行で終わります。上記の全部(コンストラクタ・getter・equals・hashCode・toString)が自動生成されます。

var person = new Person("田中", 30);
System.out.println(person.name());     // 田中
System.out.println(person.age());      // 30
System.out.println(person);            // Person[name=田中, age=30]

var person2 = new Person("田中", 30);
System.out.println(person.equals(person2)); // true(値で比較)

record の特徴と制限

特徴 詳細
不変(immutable) フィールドは final。生成後に変更不可
継承不可 extends できない(implements は可)
コンパクトコンストラクタ バリデーションを簡潔に書ける
// コンパクトコンストラクタでバリデーション
public record Person(String name, int age) {
    public Person {  // 引数リストを省略するコンパクトコンストラクタ
        if (age < 0) throw new IllegalArgumentException("年齢は0以上");
        name = name.strip();  // 前後空白を取る
    }
}

実務では「DTOやAPIのレスポンスモデルを record に置き換える」だけで、クラスファイルが劇的にスッキリします。

4. sealed クラス(Java 17 から正式へ)

継承できるクラスを明示的に限定する 機能です。

// Shape を継承できるのは Circle, Rectangle, Triangle だけと宣言
public sealed class Shape
    permits Circle, Rectangle, Triangle {}

public final class Circle extends Shape {
    private final double radius;
    // ...
}
public final class Rectangle extends Shape {
    private final double width, height;
    // ...
}
public non-sealed class Triangle extends Shape {
    // non-sealed = さらに継承を許可
}

何がうれしいのか

sealed がある状態で switch のパターンマッチング(Java21で正式)を使うと、コンパイラが「全ての型を網羅しているか」をチェックしてくれます。

// Java 21 以降のパターンマッチング(sealed と相性が良い)
double area = switch (shape) {
    case Circle c    -> Math.PI * c.radius() * c.radius();
    case Rectangle r -> r.width() * r.height();
    case Triangle t  -> t.base() * t.height() / 2;
    // 全ケース網羅 → default 不要
};

今すぐ全部使いこなすのは難しくても、「継承を制限して安全性を上げるしくみ」として覚えておくと、後のJava21編でスムーズに繋がります。

5. instanceof のパターンマッチング(Java 16 から正式へ)

instanceof でキャストまで自動でやってくれる構文です。

Before

Object obj = "こんにちは";

if (obj instanceof String) {
    String s = (String) obj;  // キャストが必要
    System.out.println(s.toUpperCase());
}

After(Java 16 以降)

Object obj = "こんにちは";

if (obj instanceof String s) {  // キャストと変数宣言を同時に
    System.out.println(s.toUpperCase());  // そのまま s として使える
}

地味ですが、instanceof → キャスト → 使う、の3ステップが1ステップになります。レガシーなコードを整理するときにどんどん使えます。

6. NPEメッセージの詳細化(Java 14)

これは「機能」というよりデバッグ体験の改善ですが、知っておいて損はありません。

Before(Java 13 以前)

// NullPointerException: null
user.getAddress().getCity().toUpperCase();

どこが null なのか、スタックトレースの行番号を見てコードと突き合わせるしかありませんでした。

After(Java 14 以降)

// NullPointerException: Cannot invoke "Address.getCity()" because the return value of "User.getAddress()" is null
user.getAddress().getCity().toUpperCase();

「何が null だったのか」をメッセージが教えてくれます。 メソッドチェーンが長いコードで特に助かります。

実務でJava11→17に上げるときの注意

--illegal-access の撤廃(Java 17)

Java9でモジュールシステムが導入されて以来、JDK内部のAPIへのリフレクションアクセスが制限されてきました。Java16で警告に変わり、Java17では --illegal-access オプション自体が削除されました。

古いライブラリ(特にByte Buddy、Javassist を使うフレームワーク系)がこの影響を受けることがあります。Spring Boot 3 は Java 17 ベースで設計されているので、最新の Spring を使う限りは問題ありません。旧来の Spring Boot 2.x を使いながら Java17 に上げる場合は注意が必要です。

おわりに

Java11→17の5バージョン分をまとめて見てきました。

  • switch: フォールスルーのバグを撲滅
  • テキストブロック: JSON・HTML の文字列がストレスゼロに
  • record: データクラスが1行になる
  • sealed: 継承の範囲を安全に制限
  • instanceof のパターンマッチング: キャストを1ステップに
  • NPEメッセージの詳細化: デバッグが楽になった

どれも「コードが読みやすく・安全になる」方向の変化です。Java8から17に上げたとき「あ、Javaって進化してたんだ」と初めて気づきました——飛び級だったので。

次回は Java17→21編。Virtual Threads(仮想スレッド)というJavaの並行処理を根本から変える機能が登場します。Spring Boot 3.2以降との組み合わせも熱い回です。一緒にレベルアップを目指しましょう。

→ [Java17→21編](公開後にリンクを追加します)


参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?