LoginSignup
110

More than 3 years have passed since last update.

Java 15新機能まとめ

Last updated at Posted at 2020-09-18

Java 15が2020/9/15にリリースされました。
https://mail.openjdk.java.net/pipermail/announce/2020-September/000291.html

Java SE 15 Platform JSR 390
JDK 15 GA Release

こちらの動画でざっくりと説明しています。
Java 15を軽く紹介

MacやLinuxでのインストールにはSDKMAN!をお勧めします

Oracle OpenJDK以外に無償で商用利用できるディストリビューションとしては、次のようなものがあります。

アップデートは10月に15.0.1が、翌年1月に15.0.2がリリースされることになります。

Oracle JDKは開発用途には利用できますが、商用利用にはJava SE Subscriptionを購入する必要があります。

JEP

大きめの変更はJEPでまとまっています。
http://openjdk.java.net/projects/jdk/15/

今回は14のJEPが取り込まれました。ただ、deprecateやremoveが4つ、すでにプレビューで出ていたものが6つあり、今回新たに取り込まれたのは実質4つとなります。
JEP 339: Edwards-Curve Digital Signature Algorithm (EdDSA)
JEP 360: Sealed Classes (Preview)
JEP 371: Hidden Classes
JEP 372: Remove the Nashorn JavaScript Engine
JEP 373: Reimplement the Legacy DatagramSocket API
JEP 374: Disable and Deprecate Biased Locking
JEP 375: Pattern Matching for instanceof (Second Preview)
JEP 377: ZGC: A Scalable Low-Latency Garbage Collector (Production)
JEP 378: Text Blocks (Standard)
JEP 379: Shenandoah: A Low-Pause-Time Garbage Collector (Production)
JEP 381: Remove the Solaris and SPARC Ports
JEP 383: Foreign-Memory Access API (Second Incubator)
JEP 384: Records (Second Preview)
JEP 385: Deprecate RMI Activation for Removal

分野ごとにまとめていきます。

言語機能

言語仕様にかかわる変更としては次のようなものがあります。
JEP 360: Sealed Classes (Preview)
JEP 375: Pattern Matching for instanceof (Second Preview)
JEP 384: Records (Second Preview)
JEP 378: Text Blocks (Standard)

JEP 360: Sealed Classes (Preview)

Sealedクラスは、継承できるクラスを限定する機能です。仕様の名前は「クラス」 となってますが、interfaceでも使えます。
クラス名のあとに permitsで継承クラスを指定していくという感じになります。

public abstract sealed class Shape
    permits Circle, Rectangle, Square {...}

そうするとShapeクラスを継承できるのはCircle、Rectangle、Squareに限定されます。Circle、Rectangle、Squareは必ず定義する必要があります。
これらのクラスは、モジュール化しているのであれば同じモジュール内、モジュール化していないのであれば同じパッケージ内である必要があります。
また、permitsされるクラスが同一のソースだけにあるのであれば、permitsは省略できます。
permitsするクラスは定義のときにsealedかnon-sealedかfinalをつける必要があります。recordは暗黙的にfinalになるのでなにもつける必要はありません。

現状ではほとんど使いどころがないです。
例えば、Shapeの継承先は確定しているので、次のコードはコンパイル通ってほしいところですが、エラーになります。

String getName(Shape s) {
  if (s instanceof Circle) {
    return "円";
  } else if (s instanceof Rectangle) {
    return "四角";
  } else if (s instanceof Square) {
    return "正方形";
  }
}

まだドラフトですがPattern matching for switch (Preview)が導入されてswitch式/文でパターンマッチが使えるようになれば、次のようにdefaultなしで書ける予定です。

String getName(Shape s) {
  switch (s) {
    case Circle c -> return "円";
    case Rectangle r -> return "四角";
    case Square q -> return "正方形";
  }
}

もちろん、ここでswitch式を使うこともできますね。

String getName(Shape s) {
  return switch (s) {
    case Circle c -> "円";
    case Rectangle r -> "四角";
    case Square q -> "正方形";
  }
}

ということで、現時点でのSealedクラスは今後の拡張のための下準備というところです。
背景にある考え方は、次の記事で言語設計者であるBrian Goetzが解説しています。
Java 注目の機能:Sealed クラス

JEP 384: Records (Second Preview)

データ保持用のクラスとしてrecordがJava 14にpreview機能として入ったRecordsですが、フィードバックを反映したSecond Previewになりました。Java 16で次のJEPで正式化という流れになりそうです。
JEP draft: Record Classes

変更点

・コンストラクタがpublicである必要があったものが、record本体に合わせた可視性で定義できるようになりました。
・引数リストを省略したコンストラクタでコンポーネントフィールドに値を割り当てるとコンパイルエラーになります。

定義

recordとして定義します。

record Foo(int x, int y) {}

他のクラスは継承できません。

record レコード名(状態) { 定義 }

これは次のようなクラスになります。

class Foo extends Record {
  // 状態がprivate finalとして定義
  private final int x;
  private final int y;
  // 追加のインスタンスフィールドは定義できない

  // 状態をフィールドに設定するコンストラクタが定義される
  public Foo(int x, int y) {
    this.x = x;
    this.y = y;
  }

  // 状態と同じ名前のメソッドが定義される
  public int x() {
    return x;
  }
  public int y() {
    return y;
  }

  // 状態を反映するhashCodeが定義される
  public int hashCode() { ... }
  // 状態を比較するequals が定義される
  public boolean equals() { ... }
  // 状態を表示するtoStringが定義される
  public String toString() { ... }
}

取得用メソッドは定義されますが、値設定用のメソッドは定義されません。つまり、イミュータブルなオブジェクトとなります。また、get/setではないことからJava Beanとの互換性もありません。
hashCode()メソッドやequals()メソッドなどは実際にはinvokeDynamicで実行時に実装コードが生成されます。

次のように、状態の検査や正規化にコンストラクタが定義できます。コンストラクタの引数リストは省略できます。

record Range(int lo, int hi) {
  Range {
    if (lo < 0) lo = 0; // 正規化
    if (lo > hi) throw IllegalArgumentException();
    // 設定されなかったフィールドはあとで設定される
  }
}

Java 14ではコンストラクタはpublicである必要がありましたが、Java 15ではrecordがパッケージプライベートであればコンストラクタもパッケージプライベートにできるというように、record本体の可視性にあわせれるようになりました。
また、コンストラクタの引数リストを省略した場合、Java 15からはコンポーネントフィールドに値を割り当てるとコンパイルエラーになります。

record Range(int lo, int hi) {
  Range {
    this.lo = 3; // コンパイルエラー
  }
}

staticではないinner classの中でrecordを定義することはできません。
次のようなコードをコンパイルするとrecord declarations not allowed in inner classesというエラーになります。

public class NestedRecord {
  class Foo {
      record Bar(int x){}
  }
}

この制約はJava 16で正式化されるときに外れる予定です。
次のようにstaticなクラスの中でのrecord定義はコンパイルできます。

public class NestedRecord {
  static class Foo {
      record Bar(int x){}
  }
}

APIの拡張

recordはjava.lang.Recordを継承したクラスになります。

次のようにRecordを継承するコードを書こうとするとrecords cannot directly extend java.lang.Recordというエラーになります。

class Foo extends Record {
}

Classクラスにはレコード関連のメソッドが追加されています。
isRecordメソッドで型がrecordかどうか判定できます。またgetRecordComponentsでrecordで定義されたコンポーネントを取得することができます。ただし、値の取得はRecordComponent経由では行えないので、Field経由で行うようです。

jshell> Foo.class.isRecord()
$9 ==> true

jshell> Foo.class.getRecordComponents()
$10 ==> RecordComponent[2] { int x, int y }

jshell> String.class.isRecord()
$11 ==> false

jshell> String.class.getRecordComponents()
$12 ==> null

型名としてのrecordの制限

recordという名前を型(クラス・インタフェース・レコード型など)につけることは制限されています。
--enable-previewを付けた状態ではエラーになります。

$ jshell --enable-preview
|  JShellへようこそ -- バージョン15
|  概要については、次を入力してください: /help intro

jshell> class record{}
|  エラー:
|  ここでは'record'は許可されません
|    リリース14から'record'は制限された型名であり、型の宣言に使用できません
|  class record{}
|        ^

jshell> String record=""
record ==> ""

jshell> record Rec(int record){}

jshell> Rec record=new Rec(3)
record ==> Rec[record=3]

enumと違いキーワードではないので、変数名やフィールド、recordのコンポーネント名にはrecordを使えます。
--enable-previewを付けない状態では、警告が表示されます。

$ jshell
|  JShellへようこそ -- バージョン15
|  概要については、次を入力してください: /help intro

jshell> class record{}
|  警告:
|  'record'は将来のリリースで制限された型名になる可能性があり、型の宣言での使用、または配列の要素タイプとしての使用はできなくなる可能性があります
|  class record{}
|        ^
|  次を作成しました: クラス record

今後の改善

standardになるときには、現状ではstaticではないinner classではrecordを入れ子にすることができないけど対応するようです。
java.lang.Recordをinline class対応のためにインタフェースにするのはどうだろう?という話があったのだけど、ドロップしたようです。
java.lang.Record as Interface

JEP 378: Text Blocks (Standard)

複数行文字列リテラルです。

歴史

Java 12でRaw String literalsとして提案されましたが、`(バッククオート)を使う仕様だったことから、記号の浪費という指摘もあって却下されました。
JEP 326: Raw String Literals (Preview)

その後さまざまな言語での複数行文字列リテラルを研究して、Java 13でText Blockとして"""で囲む仕様がPreviewとして入りました。
JEP 355: Text Blocks (Preview)

Java 14ではフィードバックを基に改行やスペースのエスケープが入ってPreview 2になりました。
JEP 368: Text Blocks (Second Preview)

そしてJava 15で標準機能になりました。

機能

改行などを含んだ文字列を定義できます。"""で囲みます。

// same as "You can write\ntwo line string.\n"
var str = """
  You can write
  two line string.
  """;

開始の"""のあとには文字列を続けれません。また、インデントは"""や内部の文字列で一番浅いところが基準になります。

var str = """
..You can write
..two line string.
  """;
var str = """
..  You can write
..  two line string.
  """;

改行をエスケープすることもできます。

var str = """
  You can write \
  two line string, \
  but this is single.
  """;

これは"You can write two line string, but this is single."になります。

行末のスペースは削除されます。
そこで、行末にスペースが必要なときは\sを入れてスペースが必要なことを示します。

var str = """
  test\s
  test \s
  """;

これは"test_\ntest__\n"になります。(Qiitaでは複数スペースをいれてもスペースひとつになってしまう)

文字列への変数の埋め込みはできません。その代わりにformattedメソッドがインスタンスメソッドとして用意されて、次のように書けるようになりました。

var str = """
  こんにちは、%sさん。
  今日はいい天気ですね。
  """.formatted("きしだ");

API

Text BlocksとともにいくつかのAPIが正式化されています。いままでにも入っていましたが、これらのAPIも--enable-previewなしで使えるようになりました。

stripIndent

stripIndentは複数行文字列から一番浅いインデント分の空白を取り除きます。
たとえば、次のような文字列があるとします。

  test
    test
  test

ここからインデント空白を取り除くと次のようになります。

test
  test
test

ただし、末尾に改行文字がある場合はインデント0が一番浅いとみなされます。

jshell> """
   ...>   test
   ...>     test""".stripIndent()
$28 ==> "test\n  test"

jshell> """
   ...>   test
   ...>     test
   ...> """.stripIndent()
$29 ==> "  test\n    test\n"
translateEscapes
jshell> "test\\n\\ttest"
$10 ==> "test\\n\\ttest"

jshell> System.out.println($10)
test\n\ttest

jshell> System.out.println($10.translateEscapes())
test
        test
formatted
jshell> "みかんは%dえん".formatted(100*11/10)
$14 ==> "みかんは110えん"

jshell> String.format("みかんは%dえん", 100*11/10)
$15 ==> "みかんは110えん"

JEP 375: Pattern Matching for instanceof (Second Preview)

パターンマッチングです。
instanceofを使ったパターンマッチが14でプレビューとして入りましたが、そのまま変更なしにセカンドプレビューとしてJava 15に入っています。

値 instanceof パターンで、値をマッチさせることができます。
パターンは、定数か変数定義です。変数定義の場合には、型が一致していた場合にtrueになりその変数に値が割り当てられます。

if (x instanceof Integer i) {
    // can use i here
}

switchで使えるようになれば便利ですが、これは別のJEPで定義されていて、Java 16以降に持ち越されています。
JEP draft: Pattern matching for switch (Preview)

2018年の文章だけど、パターンマッチングについて全体像はこんな感じぽい
http://cr.openjdk.java.net/~briangoetz/amber/pattern-match.html

API

JavaDocはこちら。
Overview (Java SE 15 & JDK 15)
変更点はこちら。
DRAFT: API Differences Between Java SE 14 (build 36) & Java SE 15 (build 36)

JEPでは次のようなものがあります。

JEP 371: Hidden Classes
JEP 373: Reimplement the Legacy DatagramSocket API
JEP 339: Edwards-Curve Digital Signature Algorithm (EdDSA)
JEP 383: Foreign-Memory Access API (Second Incubator)
JEP 372: Remove the Nashorn JavaScript Engine
JEP 385: Deprecate RMI Activation for Removal

JEPになってないものでは、Unicode 13への対応やMath.absExactの追加があります。

JEP 371: Hidden Classes

Javaでは実行時にクラスを作成することがよく行われます。
たとえばラムダ式では実行時に該当するインタフェースを実装したクラスが生成されます。ほかにもSpringなどのフレームワークではサブクラスを生成してAOPを実現するといったことが行われています。
現状ではそのように作成されたクラスでもリフレクション経由で外部から利用することができていますが、Hidden Classとすることで利用できないようにします。
そうすることで、生成されたクラスが利用される範囲を限定でき、クラスアンロードを効率よく行うことができるようになります。
また、UnsafeのdefineAnonymousClassの代替として使えるようになるので、このdefineAnonymousClassをdeprecatedにします。

JEP 373: Reimplement the Legacy DatagramSocket API

Java 13でTCPソケット用のAPIが実装されなおしています。それに引き続いて、Java 15ではUDPソケットをより簡潔で現代的に実装しなおしました。
このことでメンテナンスが容易になるとともに、プロジェクトLoomで開発されている仮想スレッドに対応しやすくなっています。

JEP 339: Edwards-Curve Digital Signature Algorithm (EdDSA)

エドワーズ暗号デジタル署名の実装です。
TLS1.3での鍵交換に使えるのかな。
RFC 8032の実装ということになります。
https://tex2e.github.io/rfc-translater/html/rfc8032.html

JEP 372: Remove the Nashorn JavaScript Engine

NashornはJava 8でRhinoに代わるJavaScriptエンジンとして入りました。
JEP 174: Nashorn JavaScript Engine

しかし、JavaScriptエンジンのメンテナンスをOpenJDKとして行うのは重いということでJava 11でDeprecatedになっていましたが、15で削除されます。
JEP 335: Deprecate the Nashorn JavaScript Engine

代替は用意されていませんが、JavaScriptエンジンが必要になったときにはGraalVMで動くGraalJSを使いましょうということのようです。
JavaScriptエンジンが必要ならOpenJDKではなくGraalVMを使いましょうということになりますが、OpenJDKで動かすこともできます。
OpenJDKでGraalJSを動かす方法はこちら。
Improve React.js Server-Side Rendering by 150% with GraalVM – Logico Inside

JEP 383: Foreign-Memory Access API (Second Incubator)

ヒープ外のメモリをアクセスする方法としては、ByteBufferを使う方法やUnsafeを使う方法、JNIを使う方法がありますが、それぞれ一長一短があります。
ByteBufferでdirect bufferを使う場合、intで扱える範囲の2GBまでに制限されたり、メモリの解放がGCに依存したりします。
Unsafeの場合は、性能もいいのですが、名前が示すとおり安全ではなく、解放済みのメモリにアクセスすればJVMがクラッシュします。
JNIを使うとCコードを書く必要があり、性能もよくないです。

ということで、ヒープ外のメモリを直接扱うAPIがJava 14でインキュベータモジュールとして導入されたわけです。そしてJava 15でセカンドインキュベータになっています。
次のようなコードになります。

VarHandle intHandle = MemoryHandles.varHandle(int.class);

try (MemorySegment segment = MemorySegment.allocateNative(100)) {
   MemoryAddress base = segment.baseAddress();
   for (int i = 0 ; i < 25 ; i++) {
        intHandle.set(base.offset(i * 4), i);
   }
}

コンパイル、実行では--add-modules jdk.incubator.foreignを付けてモジュールを読み込む必要があります。

Java 14にくらべてMemoryHandlesからVarHandleを得るメソッドが拡充してますね。JShellでの補完は次のようになっています。

jshell> MemoryHandles.
asAddressVarHandle(   asUnsigned(           class                 collectCoordinates(   dropCoordinates(
filterCoordinates(    filterValue(          insertCoordinates(    permuteCoordinates(   varHandle(
withOffset(           withStride(

Java 14では次の3つのメソッドしかありませんでした。

jshell> MemoryHandles.
class         varHandle(    withOffset(   withStride(

JEP 385: Deprecate RMI Activation for Removal

Java 1.2で導入されたRMI Activationですが、時代遅れってことで削除に向けてまずはDeprecatedになりました。
※ RMI自体は消えません
rmidというデーモンにRMIサービスを登録して、必要に応じてリモートオブジェクトを起動するという仕組みでした。Java 8からは必須ではなくなっています。rmidも将来的に削除されるようです。
java.rmi.activationパッケージがDeprecatedになっています。
java.rmi.activation (Java SE 15 & JDK 15)
Java Remote Method Invocation: 7 - Remote Object Activation

Math.absExact

絶対値を取得するMath.absInteger.MIN_VALUEを渡すと、そのままInteger.MIN_VALUEが返ってきてしまいます。
Math.absExactではInteger.MIN_VALUEを渡すと例外が投げられます。

jshell> Math.abs(Integer.MIN_VALUE)
$2 ==> -2147483648

jshell> Math.absExact(Integer.MIN_VALUE)
|  例外java.lang.ArithmeticException: Overflow to represent absolute value of Integer.MIN_VALUE
|        at Math.absExact (Math.java:1393)
|        at (#3:1)

UNICODE 13

UNICODE 13に対応しています。
[JDK-8239504] Support for Unicode 13.0 - Java Bug System

Java 11で導入されたUNICODE 10のときはJEPがあったけど、それ以来JEPは作られない感じ

JDK Flight Recorder

JFRでProcess Startイベントとかダイレクトメモリの統計がとれるようになった?
[JDK-8238665] Add JFR event for direct memory statistics - Java Bug System
[JDK-8222000] JFR: Process start event - Java Bug System

JVM

JEP 374: Disable and Deprecate Biased Locking
JEP 377: ZGC: A Scalable Low-Latency Garbage Collector (Production)
JEP 379: Shenandoah: A Low-Pause-Time Garbage Collector (Production)

JEP 374: Disable and Deprecate Biased Locking

競合のないロックのオーバーヘッドを削減する。Synchronizeされて多数のスレッドから呼ばれるCompare-and-Swap(CAS)操作のパフォーマンスを改善する。
ロックは同じスレッドから使われることが多いので、同じスレッドからのロック取得時はCASを使わないようにする。
バイアスロッキングが導入された効果は今日では明らかではなく、最近のプロセッサではアトミックな操作のパフォーマンスもあがっている
バイアスロックが効果があるのは古いアプリケーションで、VectorやHashtableのようなsynchronizeを毎回行うコレクションを使っている。
スレッドプールキューやワーカースレッドを使うような最近のアプリケーションではバイアスロックを無効にしたほうがパフォーマンスがよい
バイアスロックには複雑なコードが必要で、他の部分にも影響している。
コードの理解が難しくなり、設計変更の障害になっている。
今回は無効にして、そのうち削除する
-XX:+UseBiasedLockingで有効にできます。

JEP 377: ZGC: A Scalable Low-Latency Garbage Collector (Production)

テラバイト級のメモリに対応した低遅延のGCです。
Java 11でExperimentalとして導入されました。
JEP 333: ZGC: A Scalable Low-Latency Garbage Collector (Experimental)

当初はLinuxのみの対応でしたが、Java 14でmacOSとWindowsに対応しました。
JEP 364: ZGC on macOS (Experimental)
JEP 365: ZGC on Windows (Experimental)

そして今回プロダクションになりました。

JEP 379: Shenandoah: A Low-Pause-Time Garbage Collector (Production)

G1GCを拡張して数百GBに対応した少停止時間のGCです。
Java 12でExperimentalとして導入されました。
JEP 189: Shenandoah: A Low-Pause-Time Garbage Collector (Experimental)

今回プロダクションになりました。

ツール

JEPにはなっていませんが、rmicが削除されています。

rmicの削除

なぜかJEPになっていないですが、RMIのインタフェースからスタブクラスを作るためのrmicが削除されました。だいぶ前からスタブクラスを作る必要はなくなっていたので、基本的に使う必要なくなっていました。
※ RMI自体は消えません
Java 13のときにdeprecatedになっていたっぽい。
http://mail.openjdk.java.net/pipermail/core-libs-dev/2020-April/065622.html

OpenJDK

OpenJDK自体の変更としては次のJEPがあります。
JEP 381: Remove the Solaris and SPARC Ports

JEP 381: Remove the Solaris and SPARC Ports

Solaris/SPARC向けのソースコードを削除して、ビルドからも外されました。
OpenJDKではSolaris/SPARCに対応しなくなりますが、他のディストリビューションでは対応するかもしれません。

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
110