109
73

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Java 17新機能まとめ

Last updated at Posted at 2021-10-07

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

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

詳細はこちら
Oracle Releases Java 17
Java SE 17 Platform JSR 392
JDK 17 GA Release

APIドキュメントはこちら
Overview (Java SE 17)

変更点まとめはこちら
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!をお勧めします

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

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

Oracle JDKも「Oracle No-Fee Terms and Conditions License」で無償での商用利用ができるようになっています。ただし対外サーバーの運用には有償ライセンスが必要です

次の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
JEP 356: Enhanced Pseudo-Random Number Generators
JEP 382: New macOS Rendering Pipeline
JEP 391: macOS/AArch64 Port
JEP 398: Deprecate the Applet API for Removal
JEP 403: Strongly Encapsulate JDK Internals
JEP 406: Pattern Matching for switch (Preview)
JEP 407: Remove RMI Activation
JEP 409: Sealed Classes
JEP 410: Remove the Experimental AOT and JIT Compiler
JEP 411: Deprecate the Security Manager for Removal
JEP 412: Foreign Function & Memory API (Incubator)
JEP 414: Vector API (Second Incubator)
JEP 415: Context-Specific Deserialization Filters

言語機能

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

JEP 306: Restore Always-Strict Floating-Point Semantics
JEP 406: Pattern Matching for switch (Preview)
JEP 409: Sealed Classes

JEP 306: Restore Always-Strict Floating-Point Semantics

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

JEP 406: Pattern Matching for switch (Preview)

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

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

ガード節もあります。

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が落ちます。

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

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で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 クラス

API

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

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

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

JEP 356: Enhanced Pseudo-Random Number Generators

疑似乱数の改善

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

レンダリングにMetalフレームワークを使う

JEP 398: Deprecate the Applet API for Removal

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

JEP 403: Strongly Encapsulate JDK Internals

デフォルトになっていた。強制になる。
JEP 396: Strongly Encapsulate JDK Internals by Default

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

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

JEP 411: Deprecate the Security Manager for Removal

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

JEP 412: Foreign Function & Memory API (Incubator)

変更点

ネイティブライブラリの呼び出しを行う
外部メモリのアクセスには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でセカンドインキュベータになっています。
次のようなコードになります。

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を使わずにシンプルにかけるようになっています。

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)

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

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

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

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

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

JEP 410: Remove the Experimental AOT and JIT Compiler

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

JEP 391: macOS/AArch64 Port

M1などApple Siliconに対応しました。

109
73
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
109
73

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?