Upgrade to Pro — share decks privately, control downloads, hide ads and more …

「これもJava?」Java 8から25で何が変わったか ― 11年分の進化に追いつき...

「これもJava?」Java 8から25で何が変わったか ― 11年分の進化に追いつきたい人のためのJava再入門

「Javaってもう古くてレガシーでしょ」
そう思ったことがある方、ちょっと待ってください。Javaは過去の遺産ではなく、今も活発に進化を続けている生きた言語です。
このセッションでは、Java 8からJava 25まで間にJavaが「どう変わったのか」「なぜ変わったのか」そして「この先どうなるのか」を紐解いて紹介し、この11年間の進化を目にしていただきます。
「久しぶりにJavaを触ってみようかな」と思っている方も、「毎日使っているけど新機能はよく知らない」という方も、きっと新しい発見があるはずです。11年分の進化、一緒に追いつきましょう。

Avatar for Sugiyama Takaaki

Sugiyama Takaaki

February 18, 2026
Tweet

More Decks by Sugiyama Takaaki

Other Decks in Programming

Transcript

  1. 私のこと • 杉山貴章 • @zinbe • @zinbe.bsky.social • プログラマー •

    『プロになるJava』共著者 • 日本Javaユーザーグループ幹事 • Javaを使い始めた年:1996年
  2. 1995 2025 2005 2015 • 1995.05 Java発表 • JDK 1.0αリリース

    / 正式リリースは1996.01 • 2006 OpenJDKスタート • 開発中だったJDK 7をベースにオープンソース化
  3. 1995 2025 2005 2015 • 1995.05 Java発表 • JDK 1.0αリリース

    / 正式リリースは1996.01 • 2006 OpenJDKスタート • 開発中だったJDK 7をベースにオープンソース化 • 2009 OracleがSunを買収
  4. 1995 2025 2005 2015 • 1995.05 Java発表 • JDK 1.0αリリース

    / 正式リリースは1996.01 • 2006 OpenJDKスタート • 開発中だったJDK 7をベースにオープンソース化 • 2009 OracleがSunを買収 • 2014.03 Java 8リリース
  5. 1995 2025 2005 2015 • 1995.05 Java発表 • JDK 1.0αリリース

    / 正式リリースは1996.01 • 2006 OpenJDKスタート • 開発中だったJDK 7をベースにオープンソース化 • 2009 OracleがSunを買収 • 2014.03 Java 8リリース • 2025.09 Java 25リリース • 2026.03 Java 26リリース 「Javaの30年をふりかえる」by さくらばさん →
  6. 1995 2025 2005 2015 • 2017.10 JavaOne 2017で3つの大きな発表 • OracleJDKを有償機能も含めてOpenJDKに寄贈

    • Java EEの開発をEclipse Foundationに移管 • 後にJakartra EEへ • リリースモデルを変更 • 6ヶ月毎にメジャーバージョンを定期リリース • 6リリース毎にLTS(後に4リリース毎に変更)※ → 新機能が続々と登場し、進化が加速 ターニングポイント! ※LTSはOpenJDKではなくあくまでも各ディストリビューターが決めているだけ
  7. Javaはどうやって作られるか • OpenJDK / OpenJDKプロジェクト • 実質的にJavaを作っているオープンソースのプロジェクト • JEP (Java

    Enhancement Proposal) • OpenJDKに対する機能提案 • Javaの新機能はまずJEPとして提案され、何度かのプレビュー (Preview,Experimental,Incubator)を経て正式リリース • JDKディストリビューション • OpenJDKの成果物をビルドして利用可能な形にしたもの • Oracleもディストリビューターのひとつ
  8. いくつかの大きな流れ • 書きやすさ・学びやすさの改善 • 宣言的な記法の導入 • データ指向プログラミングへ • 並行処理の革命 •

    パフォーマンスの改善・起動時間の短縮 • Javaの外の世界との接続 • セキュリティの現代化 • 開発ツールの進化
  9. いくつかの大きな流れ • 書きやすさ・学びやすさの改善 • 宣言的な記法の導入 • データ指向プログラミングへ • 並行処理の革命 •

    パフォーマンスの改善・起動時間の短縮 • Javaの外の世界との接続 • セキュリティの現代化 今日はこの辺の話をします
  10. サンプル: 隣接要素のペアを作る 札幌 函館 旭川 帯広 釧路 小樽 苫小牧 北見

    稚内 室蘭 札幌 | 函館 函館 | 旭川 旭川 | 帯広 帯広 | 釧路 釧路 | 小樽 小樽 | 苫小牧 苫小牧 | 北見 北見 | 稚内 稚内 | 室蘭
  11. Java 8での実装例 import java.io.*; import java.nio.file.*; import java.util.*; public class

    CityPairs { public static void main(String[] args) { try { List<String> lines = Files.readAllLines(Path.of("citylist")); List<String> pairs = new ArrayList<>(); for (int i = 0; i < lines.size() - 1; i++) { pairs.add(lines.get(i) + " | " + lines.get(i + 1)); } for (String pair : pairs) { System.out.println(pair); } } catch (IOException e) { } } } 全ての行をListで取得 隣接ペアを手動ループで作成 (streamでは困難) 結果表示
  12. Java 25での実装例 import module java.base; void main() { try {

    Files.lines(Path.of("citylist")) .gather(Gatherers.windowSliding(2)) .map(w -> w.getFirst() + " | " + w.getLast()) .forEach(IO::println); } catch (IOException e) { } }
  13. import module java.base; void main() { try { Files.lines(Path.of("citylist")) .gather(Gatherers.windowSliding(2))

    .map(w -> w.getFirst() + " | " + w.getLast()) .forEach(IO::println); } catch (IOException e) { } } Java 25での実装例 モジュールのimport class宣言が無い puclic staticほげほげ何処いった? streamだけで処理 何コレ? System.outは?
  14. クラス宣言の省略&mainメソッドの簡素化(Java 25) + java.lang.IOクラスの追加 • クラス宣言が無い場合、暗黙で宣言したことになる • 自動的にjava.baseモジュールがインポートされる • インスタンス化はできない

    • mainメソッドが必要 • mainメソッドの簡素化 • インスタンスメソッドでもよい • 引数を省略できる • publicにしなくてもよい • コンソール操作のためのjava.lang.IOクラス
  15. Stream Gatherers(Java 24) • Stream APIの中間操作を拡張する • 中間操作で状態を保持できるようになる • Gatherersクラスに便利なメソッドが用意されている

    • カスタムのGathererも作れる var result = Stream.of(3, 5, 8, 7, 5, 2, 1, 9) .gather(Gatherers.windowSliding(3)) .map(f -> f.stream() .collect(Collectors.averagingDouble(x -> x))) .toList(); 隣接する3要素を取り出す 平均を計算 例: 移動平均の計算
  16. Sequenced Collections (Java 21) • 順序を持つコレクションのAPIを統一する • 最初の要素と最後の要素へのアクセス方法 • 逆順に処理する方法

    • SequencedCollection<E> , SequencedSet<E> • addFiest(), addLast(), getFirst(), getLast(), removeFirst(), removeLast() • reserved() • SequencedMap<K, V> • putFirst(), putLast(), fitstEntry(), lastEntry(), pollFirstEntry(), pollLastEntry() • reserved() • sequencedKeys(), sequencedValues(), sequencedEntrySet()
  17. import module java.base; void main() { try { Files.lines(Path.of("citylist")) .gather(Gatherers.windowSliding(2))

    .map(w -> w.getFirst() + " | " + w.getLast()) .forEach(IO::println); } catch (IOException e) { } } 以上の改善の結果がコレ モジュールのimport class宣言が無い puclic staticほげほげ何処いった? streamだけで処理 何コレ? System.outは?
  18. データを扱うクラスの例: Java 8版 public interface Shape { } public class

    Rectangle implements Shape { private final double width; private final double height; public Rectangle(double width, double height) { this.width = width; this.height = height; } // getter, equals, hashCode, toString ... } public class Circle implements Shape { private final double radius; public Circle(double radius) { this.radius = radius; } public double radius() { return this.radius; } @Override public boolean equals(Object o) { /* ... */ } @Override public int hashCode() { /* ... */ } @Override public String toString() { /* ... */ } }
  19. データを扱うクラスの例: Java 8版(つづき) public double area(Shape shape) { if (shape

    instanceof Circle) { Circle c = (Circle) shape; return Math.PI * c.radius() * c.radius(); } else if (shape instanceof Rectangle) { Rectangle r = (Rectangle) shape; return r.width() * r.height(); } throw new IllegalArgumentException("Unknown shape"); } 面積を求める instanceofで分岐 キャスト getterで値を取り出す その他の 野良Shapeも考慮
  20. データを扱うクラスの例: Java 25版 public sealed interface Shape permits Circle, Rectangle

    { } public record Rectangle(double width, double height) implements Shape { } public record Circle(double radius) implements Shape { } public double area(Shape shape) { return switch (shape) { case Circle(var r) -> Math.PI * r * r; case Rectangle(var w, var h) -> w * h; }; }
  21. データを扱うクラスの例: Java 25版 public sealed interface Shape permits Circle, Rectangle

    { } public record Rectangle(double width, double height) implements Shape { } public record Circle(double radius) implements Shape { } public double area(Shape shape) { return switch (shape) { case Circle(var r) -> Math.PI * r * r; case Rectangle(var w, var h) -> w * h; }; }
  22. switch式 (Java 14) String type = switch (day) { case

    1, 7 -> "週末"; case 2, 3, 4, 5, 6 -> "平日"; default -> throw new IllegalArgumentException("不明: " + day); }; • switchを式として宣言して値が返せるようになった • 条件の分岐は「:」ではなく「->」(矢印ラベル)で記述 • breakは不要(自動的にbreakされる) • caseはすべての可能な値を網羅しなければならない これが無いとコンパイルエラー
  23. instanceofのパターンマッチング (Java 16) if (o instanceof String) { String s

    = (String)o; System.out.println(s.length()); } if (o instanceof String s) { System.out.println(s.length()); } 型パターン oがStringオブジェクト(instanceofがtrue)である場合、 oはStringにキャストされてパターン変数sに割り当てられる 通常のinstanceof パターンマッチング
  24. switchのパターンマッチング (Java 21) switch (e) { case ラベル -> ...

    ...... } switch (e) { case パターン -> ... ...... } 通常のswitch パターンマッチング Object o = ... switch (o) { case null -> System.out.println("null"); case String s -> System.out.println("String"); default -> System.out.println("Something else"); } 使用例 oが何のオブジェクトか で分岐して、自動的に キャストされる
  25. switchのパターンマッチング (つづき) Object o = ... String result = switch

    (o) { case null -> "null"; case Integer i when i <= 0 -> "0以下の整数"; case Integer i -> "1以上の整数"; case String s when s.length() > 1 -> "1文字以上の文字列"; default -> “その他"; } • パターンにnullを指定できる • 「when」を使ってパターンに条件を付加できる (ガードパターン)
  26. レコード: データを定義する (Java 16) public class Circle implements Shape {

    private final double radius; public Circle(double radius) { this.radius = radius; } public double getRadius() { return this.radius; } @Override public boolean equals(Object o) { /* ... */ } @Override public int hashCode() { /* ... */ } @Override public String toString() { /* ... */ } } public record Circle(double radius) implements Shape { } コンパイラが補完してくれる
  27. レコードのパターンマッチング if (o instanceof Circle c) { double area =

    Math.PI * c.radius() * c.radius(); } レコードも型パターンとして指定できる if (o instanceof Circle(var r)) { double area = Math.PI * r * r; } レコード・パターン (Java 21) oがCircle(var)に一致する場合、rにo.radius()の値が割り当てられる =パターンマッチングでレコードのコンポーネントを取り出せる double area = switch (shape) { case Circle(var r) -> Math.PI * r * r; ...... };
  28. ネスト構造のレコードにも対応 record Pair(Object a, Object b) { } Object o

    = new Pair(new Pair("Hello", 1), LocalDate.of(2026,02,17)); レコードのコンポーネントがネスト構造になっているケース if (o instanceof Pair(var a, var b)) { if (a instanceof Pair(var x, var y)) { IO.println(x); IO.println(y); } System.out.println(b); } 外側と内側のコンポーネントを一度に抽出できる if (o instnaceof Pair(Pair(var x, var y), var b)) { IO.println(x); IO.println(y); IO.println(b); }
  29. Sealed Classes: 型の階層を制御する (Java 17) public sealed interface Shape permits

    Circle, Rectangle { } permitsで指定したクラスでのみ継承・実装できる クラス、インタフェースの作成者、どのコードにその継承・実装を許す のかを制御できるようにする public record Triangle(double bottom, double height) implements Shape { } コンパイルエラー public sealed interface Shape2 { record Circle2(double radius) implements Shape2 { } record Rectangle2(double width, double height) implements Shape2 { } } こういう書き方もできる
  30. Sealed Classesで網羅性を保証 switch式 = caseはすべての可能な値を網羅しなければならない public double area(Shape shape) {

    return switch (shape) { case Circle(var r) -> Math.PI * r * r; case Rectangle(var w, var h) -> w * h; }; } ShapeのサブクラスはCircleとRectangleしかあり得ないので、 全ケースを網羅している(defaultが不要)
  31. パターンマッチングはまだまだ進化する • プリミティブ型パターン (Java 26で4thプレビュー) • 配列パターン • 定数パターン •

    for文でのパターンマッチング • ローカル変数宣言でのパターンマッチング • メソッド宣言でのパターンマッチング • レコード・パターンを非レコードにも拡張する • etc...
  32. プリミティブ型パターン switch (x.getStatus()) { case 0 -> "okay"; case 1

    -> "warning"; case 2 -> "error"; default -> "unknown: " + x.getStatus(); } • 一致した値を変数として取り出して使える • ガードパターンで条件付けもできる switch (x.getStatus()) { case 0 -> "okay"; case 1 -> "warning"; case 2 -> "error"; case int i when i < 0 -> criticalIssue(i); case int i -> "unknown: " + i; } これまで パターンマッチング
  33. Javaでのデータ指向プログラミング • データ → レコード • 選択 → Sealedクラス、Sealedインタフェース •

    プロセス → パターンマッチング レコード以外のクラスにも拡張できるのでは? • データを格納するクラスでも、可変にしたい場合はレコード にはできない • レコードの利便性(最小記述で定義できる)を可変なクラス にも応用できないか?
  34. キャリアクラス? Data-Oriented Programming for Java: Beyond Records https://openjdk.org/projects/amber/design-notes/beyond-records class Point(int

    x, int y) { private component int x; // 可変 private component int y; // 可変 /* 自動生成されたコンストラクタ */ /* 自動生成された x, y へのアクセサメソッド */ /* 自動生成された equals, hashCode, toString, etc */ }
  35. 100個のURLを並行取得する: Java 8版 ExecutorService executor = Executors.newFixedThreadPool(20); List<Future<String>> futures =

    new ArrayList<>(); for (String url : urls) { futures.add(executor.submit(() -> { return fetchBody(url); })); } for (Future<String> f : futures) { try { System.out.println(f.get()); } catch (ExecutionException e) { e.printStackTrace(); } } executor.shutdown(); URL毎にスレッドを起動して処理 例外処理がスレッドごとにバラバラ 最大スレッド数を指定 手動でシャットダウン
  36. 100個のURLを並行取得する: Java 25版 try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { List<Future<String>>

    futures = urls.stream() .map(url -> executor.submit(() -> fetchBody(url))) .toList(); for (var f : futures) { IO.println(f.get()); } } catch (ExecutionException e) { e.printStackTrace(); }
  37. 100個のURLを並行取得する: Java 25版 try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { List<Future<String>>

    futures = urls.stream() .map(url -> executor.submit(() -> fetchBody(url))) .toList(); for (var f : futures) { IO.println(f.get()); } } catch (ExecutionException e) { e.printStackTrace(); } URL毎にバーチャルスレッド を起動して処理 バーチャルスレッド用 Executor try-with-resourcesで自動シャットダウン 見た目そこまで大きく変わってない ←そこが良い
  38. バーチャルスレッド (Java 21) • 従来のスレッド = OSネイティブのスレッドと1対1でマッピング • オーバーヘッドやメモリ消費量が大きい •

    大量のスレッドだとパフォーマンスが上がらない • バーチャルスレッド • JVMがスケジューリングするユーザレベル・スレッド • 1つのOSスレッドの上で複数のバーチャルスレッドを割り当てて実行 • 数百万規模のバーチャルスレッドの同時実行にも耐えられる 大量の同時接続を処理する必要がある大規模なWebアプリケーションやマ イクロサービス、データ取得のI/O待ちが発生するデータベースアクセス などで特に大きな効果が期待できる
  39. Java 8のThreadLocal • 同じスレッド内で値を共有する仕組み • フィールドのような共有変数をスレッドセーフで使える • デメリット • 可変な変数しか扱えない

    • 明示的に指示しない限りスレッドが終了しても変数が削除されない • スレッドプール内でメモリリークのリスク • スレッドが大量にある場合オーバーヘッドが大きい バーチャルスレッドの登場で、想定外の大量のスレッドが 並行起動するようになり、デメリットが深刻になった
  40. Scoped Values (Java 25) • スレッド単位で変数を共有する仕組み(ThreadLocalと同じ) • スレッドごとの変数は不変 • 高速な動作

    • 変数は、最初に値を共有したメソッドが終了した時点で自動的に破棄 static final ScopedValue<String> USER = ScopedValue.newInstance(); void main() { ScopedValue.where(USER, "Gihyo").run(() -> { greet(); }); } void greet() { IO.println("Hello, " + USER.get()); } Try-with-resourcesのように 自動的にスコープを管理する スコープを抜けると値が破棄される 共有された値を取り出す ScopedValueを作成
  41. Structured Concurrency (Java 26で6thプレビュー) • 複数の並行タスクを構造化されたスコープ内で管理する仕組み • メリット • タスクのライフサイクルを一元管理できる

    • エラーハンドリングやキャンセル処理を一元化できる • どのサブタスクがどのスコープに属しているのかを明確化できる
  42. Structured Concurrencyの例 Response handle() throws InterruptedException { try (var scope

    = StructuredTaskScope.open()) { Subtask<String> user = scope.fork(() -> findUser()); Subtask<Integer> order = scope.fork(() -> fetchOrder()); scope.join(); return new Response( user.get(), order.get() ); } } サブタスクを開始 スコープを抜けると全てのサブタスクが自動的に完了またはキャンセルされる 全てのサブタスクの終了を待つ 結果を取得 スコープの開始
  43. GCの継続的な改善 • CMS GCの廃止(Java 14で削除) • G1GCの継続的改善 • 大規模なヒープを持つ環境でのレイテンシ大幅改善(Java 17)

    • 巨大オブジェクト処理の最適化(Java 18) • バリアの実装の簡素化(Java 24) • 同期を減らしてスループットを向上(Java 26) • ZGC(Java 15〜) • テラバイト級ヒープでも10ms以下のレイテンシ • 世代別ZGC(Java 21、Java 23でデフォルト) • Shenandoah GC(Java 15) • ヒープサイズに関係なく最小の停止時間を維持 • 世代別Shenandoah GC (Java 25)
  44. オブジェクトを軽くする • Javaオブジェクトのヘッダーサイズは96〜128bit • しかし大半のJavaアプリのオブジェクトの平均サイズは32〜64byte • ヘッダーだけで20%以上も占めていることになる • 大量のオブジェクトを使うアプリでは馬鹿にできない Project

    Lilliput • 未使用ビットの見直しなどでヘッダーを64bitまで縮小 (Java 25) • JEP 519: Compact Object Headers • JEP draft: Compact Object Headers by Default • さらに32bitまで縮小 (未定) • JEP draft: 4-byte Object Headers
  45. Project Leydenの成果物 • クラスロード後のイメージをキャッシュ化して再利用 (Java 24) • JEP 483: Ahead-of-Time

    Class Loading & Linking • その事前キャッシュを簡単に作れるようにする (Java 24) • JEP 514: Ahead-of-Time Command-Line Ergonomics • メソッドのプロファイリング結果をキャッシュ化して再利用 (Java 25) • JEP 515: Ahead-of-Time Method Profiling • JEP 483を拡張して任意のGC(ZGCなど)でも使えるようにする (Java 26) • JEP 516: Ahead-of-Time Object Caching with Any GC • ネイティブイメージの作成 (未定) • JEP Draft: Ahead-of-Time Code Compilation
  46. Project Panama • Foreign Function and Memory(FFM) API (Java 22)

    • jextract (早期アクセス) • ネイティブライブラリのFFM API用のJavaバインディングを機械的 に生成してくれるコマンドラインツール • Vector API • CPUのベクトル命令を使えるようにするAPI • Java 26で11番目のインキュベーター • Project ValhallaでValue Typeが正式リリースされるまでおあずけ JavaとOSネイティブのコードやライブラリとの相互連携を 強化する
  47. FFM API (Java 22) • JNI(Java Native Interface)を置き換える • Unsafeを使わなくてもネイティブメモリを使えるようにする

    • Unsafeの削除に向けた代替手段を提供 Javaからネイティブライブラリやネイティブメモリーに簡単 かつ安全にアクセスできるようにするAPI 「Javaでも快適に電子工作ができる。そう、FFM APIを使えばね」by 杉山 →
  48. 暗号化技術の進化と、ポスト量子暗号に向けて • 新しい暗号技術を取り入れる • EdDSA署名アルゴリズムのサポート (Java 17) • TLS実装の改良 •

    量子コンピュータ時代に備える • KEM(鍵カプセル化メカニズム)のサポート (Java 21) • KEM = 安全でない通信経路で秘密鍵を安全に扱う技術 • モジュール格子暗号によるキー交換・電子署名のサポート (Java 24) • KDF API(キー導出関数API)のサポート (Java 25) • KDF = 暗号鍵を安全に生成するための仕組み • PEM API: 暗号鍵・証明書のPEM形式を扱うAPI (プレビュー中) 「JEP 496とJEP 497から学ぶ耐量子計算機暗号入門」by 浅野正貴 →