Java 17新機能まとめ

Java 17が2021/9/14にリリースされました。

http://mail.openjdk.java.net/pipermail/jdk-dev/2021-September/006037.html

機能的に多くのプログラマに関係ありそうな変更はApple Siliconに対応したくらいですが、LTSであるということが大きいと思います。

詳細はこちら
[Oracle Releases Java 17](https://www.oracle.com/news/announcement/oracle-releases-java-17-2021-09-14/)
[Java SE 17 Platform JSR 392](https://openjdk.java.net/projects/jdk/17/spec/)
[JDK 17 GA Release](https://jdk.java.net/17/)

APIドキュメントはこちら
[Overview (Java SE 17)](https://cr.openjdk.java.net/~iris/se/17/build/latest/api/)

変更点まとめはこちら
https://docs.oracle.com/en/java/javase/17/migrate/significant-changes-jdk-release.html#GUID-339B2415-8BA8-438C-93AF-F9C746F7CE45

今回はLTSなので、前回LTSであるJava 11からの差分もまとめられています。
JEPの差分はこちら。
https://openjdk.java.net/projects/jdk/17/jeps-since-jdk-11

APIの差分はこちら。
https://docs.oracle.com/en/java/javase/17/docs/api/new-list.html

MacやLinuxでのインストールには[SDKMAN!](https://sdkman.io/)をお勧めします

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

- [Oracle JDK](https://www.oracle.com/java/technologies/downloads/) ※対外サーバーの運用には有償ライセンスが必要です
- [Adoptium](https://adoptium.net/?variant=openjdk17&jvmVariant=hotspot)
- [Azul Zulu](https://www.azul.com/downloads/?version=java-17-lts&package=jdk)
- [Liberica JDK](https://bell-sw.com/pages/downloads/#/java-17-lts%20/%20current)
- [Amazon Corretto 17](https://docs.aws.amazon.com/corretto/latest/corretto-17-ug/downloads-list.html)
- [Microsoft Build of OpenJDK](https://www.microsoft.com/openjdk)

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

Oracle JDKも「[Oracle No-Fee Terms and Conditions License](https://www.oracle.com/downloads/licenses/no-fee-license.html)」で無償での商用利用ができるようになっています。ただし対外サーバーの運用には有償ライセンスが必要です

次のLTSは2年後の21になる模様。
https://mreinhold.org/blog/forward-even-faster

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

今回は14個のJEPが取り込まれました。すでにプレビューなどで出ていたものが3つあり、新たに取り込まれたものは11です。非推奨・削除が4つあるので、新機能としては7つですね。
[JEP 306: Restore Always-Strict Floating-Point Semantics](https://openjdk.java.net/jeps/306)
[JEP 356: Enhanced Pseudo-Random Number Generators](https://openjdk.java.net/jeps/356)
[JEP 382: New macOS Rendering Pipeline](https://openjdk.java.net/jeps/382)
[JEP 391: macOS/AArch64 Port](https://openjdk.java.net/jeps/391)
[JEP 398: Deprecate the Applet API for Removal](https://openjdk.java.net/jeps/398)
[JEP 403: Strongly Encapsulate JDK Internals](https://openjdk.java.net/jeps/403)
[JEP 406: Pattern Matching for switch (Preview)](https://openjdk.java.net/jeps/406)
[JEP 407: Remove RMI Activation](https://openjdk.java.net/jeps/407)
[JEP 409: Sealed Classes](https://openjdk.java.net/jeps/409)
[JEP 410: Remove the Experimental AOT and JIT Compiler](https://openjdk.java.net/jeps/410)
[JEP 411: Deprecate the Security Manager for Removal](https://openjdk.java.net/jeps/411)
[JEP 412: Foreign Function & Memory API (Incubator)](https://openjdk.java.net/jeps/412)
[JEP 414: Vector API (Second Incubator)](https://openjdk.java.net/jeps/414)
[JEP 415: Context-Specific Deserialization Filters](https://openjdk.java.net/jeps/415)

# 言語機能
言語仕様にかかわる変更としては次のようなものがあります。

[JEP 306: Restore Always-Strict Floating-Point Semantics](https://openjdk.java.net/jeps/306)
[JEP 406: Pattern Matching for switch (Preview)](https://openjdk.java.net/jeps/406)
[JEP 409: Sealed Classes](https://openjdk.java.net/jeps/409)

## [JEP 306: Restore Always-Strict Floating-Point Semantics](https://openjdk.java.net/jeps/306)

常に厳密な浮動小数点演算をする。strictfpをつけるのと同じ動作になる。
もともと厳密な浮動小数点演算は負荷が高いからCPU任せをデフォルトにして厳密に演算したい場合はstrictfpをつけるというふうにJava 1.2でなったけど、もうSSE2で対応できて負荷もなくなったので常にstrictfpでやろう、ということらしい。

## [JEP 406: Pattern Matching for switch (Preview)](https://openjdk.java.net/jeps/406)

Switch文・式でパターンマッチングが使えます。

```java
Object o;
System.out.println(switch (o) {
  case String s -> "  %s  ".formatted(s);
  case Integer i -> "%,d".formatted(i);
  default -> o.toString();
}
```

ガード節もあります。

```java
Object o;
System.out.println(switch (o) {
  case String s && s.length() >= 5 -> s.toUpperCase();
  case String s -> "  %s  ".formatted(s);
  case Integer i -> "%,d".formatted(i);
  default -> o.toString();
}
```

switchにnullを渡すとjavacが落ちます。

```java
public class def {
  public static void main(String[] args) {
    switch (null) {
      default:
    }
  }
}
```

6月に報告されてそのままリリースされてますね。
https://bugs.openjdk.java.net/browse/JDK-8269113

## [JEP 409: Sealed Classes](https://openjdk.java.net/jeps/409)

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

```java
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の継承先は確定しているので、次のコードはコンパイル通ってほしいところですが、エラーになります。

```java
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でswitch式/文でパターンマッチを使うときには、次のようにdefaultなしで書ける予定です。

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

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

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

ということで、現時点でのSealedクラスは今後の拡張のための下準備というところです。
背景にある考え方は、次の記事で言語設計者であるBrian Goetzが解説しています。
[Java 注目の機能：Sealed クラス](https://www.infoq.com/jp/articles/java-sealed-classes/)

# API
APIの変更に関するJEPは次のようなものがあります。

[JEP 290: Filter Incoming Serialization Data](https://openjdk.java.net/jeps/290)
[JEP 356: Enhanced Pseudo-Random Number Generators](https://openjdk.java.net/jeps/356)
[JEP 382: New macOS Rendering Pipeline](https://openjdk.java.net/jeps/382)
[JEP 398: Deprecate the Applet API for Removal](https://openjdk.java.net/jeps/398)
[JEP 403: Strongly Encapsulate JDK Internals](https://openjdk.java.net/jeps/403)
[JEP 407: Remove RMI Activation](https://openjdk.java.net/jeps/407)
[JEP 411: Deprecate the Security Manager for Removal](https://openjdk.java.net/jeps/411)
[JEP 412: Foreign Function & Memory API (Incubator)](https://openjdk.java.net/jeps/412)
[JEP 414: Vector API (Second Incubator)](https://openjdk.java.net/jeps/414)
[JEP 415: Context-Specific Deserialization Filters](https://openjdk.java.net/jeps/415)


APIの変更はこちらにまとまっています。
http://cr.openjdk.java.net/~iris/se/17/build/latest/diffsFrom16%2b36/

## [JEP 356: Enhanced Pseudo-Random Number Generators](https://openjdk.java.net/jeps/356)
疑似乱数の改善

java.util.randomパッケージが作られて乱数発生まわりがまとめられています。
https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/random/package-summary.html

## [JEP 382: New macOS Rendering Pipeline](https://openjdk.java.net/jeps/382)
レンダリングにMetalフレームワークを使う

## [JEP 398: Deprecate the Applet API for Removal](https://openjdk.java.net/jeps/398)

Applet使わんやろってことでDeprecatedに

## [JEP 403: Strongly Encapsulate JDK Internals](https://openjdk.java.net/jeps/403)

デフォルトになっていた。強制になる。
[JEP 396: Strongly Encapsulate JDK Internals by Default](https://openjdk.java.net/jeps/396)

sun.misc.Unsafeなど以外の内部APIの隠蔽をデフォルトにします。
内部APIから標準APIへの移行がやりやすいようにします。

Java 9で内部APIを隠蔽する仕組みが導入されていてJava 16でデフォルト有効になりましたが、今回は--illegal-accessが削除されて強制になりました。
`--add-opens`で利用するクラスを明示する必要があります。

制限される内部APIの一覧はこちらです。
https://cr.openjdk.java.net/~mr/jigsaw/jdk8-packages-denied-by-default

```
jshell> A.class.getClassLoader().loadClass("com.sun.beans.finder.ClassFinder").getMethod("findClass", String.class).invoke("test")
|  例外java.lang.IllegalAccessException: class REPL.$JShell$15 cannot access class com.sun.beans.finder.ClassFinder (in module java.desktop) because 
module java.desktop does not export com.sun.beans.finder to unnamed module @77556fd
|        at Reflection.newIllegalAccessException (Reflection.java:385)
|        at AccessibleObject.checkAccess (AccessibleObject.java:687)
|        at Method.invoke (Method.java:559)
|        at (#5:1)
```

## [JEP 407: Remove RMI Activation](https://openjdk.java.net/jeps/407)

Java 1.2で導入されたRMI Activationですが、時代遅れってことでJava 15でDeprecatedになり、今回削除されました。
※ RMI自体は消えません
rmidというデーモンにRMIサービスを登録して、必要に応じてリモートオブジェクトを起動するという仕組みでした。Java 8からは必須ではなくなっています。rmidも削除されました。

## [JEP 411: Deprecate the Security Manager for Removal](https://openjdk.java.net/jeps/411)

アプレットを動かすときのサンドボックスなど、リモートからやってきたコードを安全に動かす仕組みをもってたけど、サーバサイドでは使われてないしアプレットなくなったらいらんやろってことでdeprecated

## [JEP 412: Foreign Function & Memory API (Incubator)](https://openjdk.java.net/jeps/412)


変更点

ネイティブライブラリの呼び出しを行う
外部メモリのアクセスにはForeign Memory Access APIを使う
JNIの代替

MemorySegmentとMemoryAddressの明確な分離
MemoryAccessインタフェースの導入。VarHandle APIの利用を最小限にする
共有セグメントのサポート
Cleanerつきセグメント

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

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

```java
import jdk.incubator.foreign.*;
import java.nio.ByteOrder;

VarHandle intHandle = MemoryHandles.varHandle(int.class, ByteOrder.nativeOrder());

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

コンパイル、実行では`--add-modules jdk.incubator.foreign`を付けてモジュールを読み込む必要があります。
パッケージもモジュール名と同様、`jdk.incubator.foreign`になっています。

MemoryHandlesからwithOffsetとwithStrideが消えましたね。

```
jshell> import jdk.incubator.foreign.*

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

Java 15ではMemoryHandlesからVarHandleを得るメソッドが拡充してました。

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

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

```
jshell> MemoryHandles.
class         varHandle(    withOffset(   withStride(
```

Java 16ではMemoryAccessというユーティリティが用意されて、VarHandleを使わずにシンプルにかけるようになっています。

```java
import jdk.incubator.foreign.*;

try (MemorySegment segment = MemorySegment.allocateNative(100)) {
   for (int i = 0 ; i < 25 ; i++) {
        MemoryAccess.setIntAtOffset(i * 4, i);
   }
}
```

## [JEP 414: Vector API (Second Incubator)](https://openjdk.java.net/jeps/414)

AVX命令のような、複数のデータに対する計算を同時に行う命令をJavaから利用できるようになります。
Project Panamaのひとつとして開発されていました。
Java 16でインキュベータとして導入されました。

使うためには実行時やコンパイル時に`--add-modules jdk.incubator.vector`をつける必要があります。

```java
import jdk.incubator.vector.*;
static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_256;

void vectorComputation(float[] a, float[] b, float[] c) {

    for (int i = 0; i < a.length; i += SPECIES.length()) { // SPECIES.length() = 256bit / 32bit -> 8
        VectorMask<Float> m = SPECIES.indexInRange(i, a.length); // 端数がマスクされる
                                                                 // a.lengthが11でiが8のとき最初の3つしか要素がないので [TTT.....]
		// FloatVector va, vb, vc;
        FloatVector va = FloatVector.fromArray(SPECIES, a, i, m);
        FloatVector vb = FloatVector.fromArray(SPECIES, b, i, m);
        FloatVector vc = va.mul(va).
                    add(vb.mul(vb)).
                    neg();
        vc.intoArray(c, i, m);
    }
}
```

利用できるのは次の6つの型です。それぞれに対応するVector型があって、これが基本になります。

|型|bit幅|Vector|
|--|--|--|
|byte|8|ByteVector|
|short|16|ShortVector|
|int|32|IntVector|
|long|64|LongVector|
|float|32|FloatVector|
|double|64|DoubleVector|

ただ、利用するにはVectorSpeciesが必要です。利用したいVectorにSPECIES_\*という定数が用意されているので、それを使います。\*は一度に計算するbit数ですね。

```
jshell> FloatVector.SP
SPECIES_128         SPECIES_256         SPECIES_512         SPECIES_64          SPECIES_MAX         SPECIES_PREFERRED   
```

MAXではそのハードウェアで使える最大、PREFERREDは推奨ビット数だけど、同じになるんじゃないのかな。ここでは256bitが推奨されて、floatが8個同時に計算できるようになっていますね。

```
jshell> FloatVector.SPECIES_PREFERRED
$11 ==> Species[float, 8, S_256_BIT]
```

ハードウェアで使えるbit数は搭載CPUに依存しますが、普通のIntel/AMDであれば256、XEONとか つよつよCPUなら512かな。M1は128でした。ハードウェアでサポートされないbit数を使おうとするとソフトウェア処理になるので遅くなります。

実際のVectorはfrom*というメソッドで取得します。fromArray、fromByteArray、fromByteBufferが用意されています。インキュベータに入る前はfromValuesがあったのですが、なくなってますね。

Vectorを得られたら、用意されたメソッドで計算します。ひととおりの算術命令はあります。

```
jshell> va.
abs()                   add(                    addIndex(               bitSize()               blend(                  
broadcast(              byteSize()              compare(                div(                    elementSize()           
elementType()           eq(                     equals(                 fma(                    getClass()              
hashCode()              intoArray(              intoByteArray(          intoByteBuffer(         lane(                   
lanewise(               length()                lt(                     max(                    min(                    
mul(                    neg()                   notify()                notifyAll()             pow(                    
rearrange(              reduceLanes(            reduceLanesToLong(      reinterpretAsBytes()    reinterpretShape(       
selectFrom(             shape()                 slice(                  sqrt()                  sub(                    
test(                   toArray()               toDoubleArray()         toIntArray()            toLongArray()           
toShuffle()             toString()              unslice(                viewAsFloatingLanes()   viewAsIntegralLanes()   
wait(                   withLane(      
```

ところで、こういったメソッド呼び出しの内部でAVX命令などを呼び出すのでは遅くなるんではという気がしますが、実際にはJVM intrinsicsという仕組みでJITコンパイラがこれらのメソッド呼び出しをネイティブ関数呼び出しに置き換えます。

## [JEP 415: Context-Specific Deserialization Filters](https://openjdk.java.net/jeps/415)

デシリアライズのフィルターをプログラミング可能にする。

## Hex formetter / parser
16進数のフォーマット、パース

```
C:\Users\naoki>jshell
|  JShellへようこそ -- バージョン17
|  概要については、次を入力してください: /help intro

jshell> HexFormat.of().toHexDigits(12345678)
$1 ==> "00bc614e"

jshell> HexFormat.of().withUpperCase().toHexDigits(12345678)
$2 ==> "00BC614E"

jshell> HexFormat.of().fromHexDigits("00bc614e")
$3 ==> 12345678

jshell>
```

http://cr.openjdk.java.net/~rriggs/hex-formatter/java.base/java/util/Hex.html
http://mail.openjdk.java.net/pipermail/core-libs-dev/2020-August/068375.html
http://mail.openjdk.java.net/pipermail/core-libs-dev/2020-August/068256.html

# JVM
JVMに関する変更はGraal削除の1件です。
[JEP 410: Remove the Experimental AOT and JIT Compiler](https://openjdk.java.net/jeps/410)

## [JEP 410: Remove the Experimental AOT and JIT Compiler](https://openjdk.java.net/jeps/410)

Java 16ではOracle OpenJDKなどでビルドに含まれていなかったGraalですが、だれも文句いわなかったということで正式に削除されました。
https://bugs.openjdk.java.net/browse/JDK-8255616

## ZGCの変更
JEPになってませんが、ZGCも改善されています。
https://malloc.se/blog/zgc-jdk17

# OpenJDK
OpenJDKに関する変更は、Apple Silicon対応の1件です。
[JEP 391: macOS/AArch64 Port](https://openjdk.java.net/jeps/391)

## [JEP 391: macOS/AArch64 Port](https://openjdk.java.net/jeps/391)
M1などApple Siliconに対応しました。