LoginSignup
90
60

More than 1 year has passed since last update.

Java 20新機能まとめ

Last updated at Posted at 2023-03-21

Java 20が2023/3/21にリリースされました。
https://mail.openjdk.org/pipermail/jdk-dev/2023-March/007517.html
The Arrival of Java 20!

今回、正式導入された大きな機能はありません。プレビューとしても新しいものはScoped Valueひとつだけです。次回Java 21がLTSなので、ウォーミングアップなバージョンという感じですね。

詳細はこちら
JDK 20 Release Notes
Java SE 20 Platform JSR 395
OpenJDK JDK 20 GA Release

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

追加されたAPIまとめはこちら
https://docs.oracle.com/en/java/javase/20/docs/api/new-list.html

APIの差分はこちら。
https://cr.openjdk.java.net/~iris/se/20/latestSpec/apidiffs/overview-summary.html

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

Oracle OpenJDK以外に無償で商用利用できるディストリビューションとしては、次のようなものがあります。(まだ用意されていないものもあります。3/22時点ではOracle JDK, Azul Zulu, Liberica JDK, Corretto 20, SapMachineがダウンロード可能です)

アップデートは4月に20.0.1が、7月に20.0.2がリリースされることになります。

JEP

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

今回は7個のJEPが取り込まれました。すでにプレビューなどで出ていたものが6つあり、新たに取り込まれたものはScoped Value 1つです。正式に取り込まれたのはありません。
また、今回は削除された機能、非推奨になった大きな機能というのもありません。
地味なバージョンになりますが、次回Java 21がLTSになるため、その準備期間という面が強いリリースです。

429: Scoped Values (Incubator)
432: Record Patterns (Second Preview)
433: Pattern Matching for switch (Fourth Preview)
434: Foreign Function & Memory API (Second Preview)
436: Virtual Threads (Second Preview)
437: Structured Concurrency (Second Incubator)
438: Vector API (Fifth Incubator)

ツール

javacコマンドやjavaコマンドで-source 7-target 7が指定できなくなりました。

言語機能

言語機能の変更としては、Record Patternsが2ndプレビューになりました。また、Pattern Matching for switchが4thプレビューになっています。

JEP 432: Record Patterns (Second Preview)
JEP 433: Pattern Matching for switch (Fourth Preview)

JEP 432: Record Patterns (Second Preview)

レコードの分解ができるパターンマッチです。
2ndプレビューになりました。Java 21で正式機能になりそうです。
Java 20では、使うときに--enable-previewが必要です。

たとえばPointというレコードを用意します。

jshell> record Point(int x, int y) {}
|  次を作成しました: レコード Point

jshell> var p = new Point(3, 5)
p ==> Point[x=3, y=5]

そうすると次のようなパターンが書けます。

jshell> p instanceof Point(var xx, var yy)
$3 ==> true

条件式と組み合わせて、パターンマッチが成り立つ時にそのまま変数の値を扱うことができます。

jshell> p instanceof Point(var xx, var yy) ?
   ...>   "x:%d y:%d".formatted(xx, yy) :
   ...>   "no match"
$4 ==> "x:3 y:5"

Java 20では拡張forでパターンマッチが使えるようになりました。

jshell> var points = List.of(new Point(0, 0), new Point(3, 5))
points ==> [Point[x=0, y=0], Point[x=3, y=5]]

jshell> for (Point(int x, int y) : points) System.out.printf("%d:%d%n", x, y)
0:0
3:5

固定値の指定は出来なさそう。

jshell> p instanceof Point(3, var yy)
|  エラー:
|  型の開始が不正です
|  p instanceof Point(3, var yy)
|                     ^

Pointを持つBoxを定義します。

jshell> record Box(Point topLeft, Point bottomRight){}
|  次を作成しました: レコード Box

jshell> var b = new Box(new Point(3, 4), new Point(7, 9))
b ==> Box[topLeft=Point[x=3, y=4], bottomRight=Point[x=7, y=9]]

そうすると、Box内のPointの値を直接取り出すことができます。

jshell> b instanceof Box(Point(var left, var top), Point(var right, var bottom)) ?
   ...>   "(%d, %d)-(%d, %d)".formatted(left, top, right, bottom) : "no match"
$8 ==> "(3, 4)-(7, 9)"

次のように命令的にレコードの中身を得ていくよりも、やりたいことがはっきりします。

b instanceof Box b ?
  "(%d, %d)-(%d, %d)".formatted(b.topLeft().x(), b.topLeft().y(), 
                                b.bottomRight().x(), b.bottomRight().y()) : 
  "no match"

ただ、なんかバグってる可能性

ネストしたRecordが2番目のコンポーネントとして定義されて、2つのcaseがあって、両方のcase句でネストしたコンポーネントの値を使う、という条件で発生してるぽい。
コードとかはこちら。
Java 20でレコードパターンとswitchの組み合わせのバグをみつけた - きしだのHatena

報告してます。
https://mail.openjdk.org/pipermail/amber-dev/2023-March/007895.html

※ 4/19 JDK 20.0.1がリリースされて修正されています。

JEP 433: Pattern Matching for switch (Fourth Preview)

パターンマッチがswitchで使えるようになります。
4thプレビューになりました。Java 21で正式機能になりそうです。
Java 20では、使うときに--enable-previewが必要です。

「といってもパターンマッチってそんなに出番ないのでは?」
と思うかもしれませんが、大きな影響が。

switchcase nullが書けるようになります。

jshell> String s = null
s ==> null

jshell> switch (s) {
   ...>     case "test" -> "テスト";
   ...>     case null -> "ぬるぽ";
   ...>     default -> "hello";
   ...> }
$13 ==> "ぬるぽ"

case nullがない場合は従来どおりNullPointerExceptionです。

jshell> switch (s) {
   ...>     case "test" -> "テスト";
   ...>     default -> "hello";
   ...> }
|  例外java.lang.NullPointerException: Cannot invoke "String.hashCode()" because "<local0>" is null
|        at (#12:1)

""nullで同じ処理を行う場合などに併記できるのが便利です。

jshell> switch(s){
   ...>     case null, "" -> "empty";
   ...>     default -> s;
   ...> }
$17 ==> "empty"

defaultnullで同じ処理したいときにアロースタイルで書けないんでは?という疑問に応えて、defaultcaseに書けるようになっています。

jshell> switch (s) {
   ...>     case "one" -> 1;
   ...>     case "two" -> 2;
   ...>     case null, default -> -1;
   ...> }
$21 ==> -1

で、パターンマッチですが、型と変数をswtichに書けるようになりました。これをタイプパターンといいます。

jshell> Object o = "abc"
o ==> "abc"

jshell> switch (o) {
   ...>     case String s -> "--%s--".formatted(s);
   ...>     case Integer i -> "%03d".formatted(i);
   ...>     default -> o.toString();
   ...> }
$35 ==> "--abc--"

タイプパターンのあとにwhenでつなげて条件を書くことでガード節とすることができます。

jshell> o = "helo"
o ==> "helo"

jshell> switch (o) {
   ...>     case String s when s.length() < 5 -> "--%s--".formatted(s);
   ...>     case String s -> s.toUpperCase();
   ...>     case Integer i -> "%05d".formatted(i);
   ...>     default -> o.toString();
   ...> }
$42 ==> "--helo--"

定数パターンとタイプパターンをひとつのswitchで使うこともできます。定数パターンに使えるのはこれまでどおり整数、文字列、enumです。

jshell> s = "helo"
s ==> "helo"

jshell> switch (s) {
   ...>     case "test" -> "テスト";
   ...>     case String s when s.length() < 5 -> s.toUpperCase();
   ...>     default -> "too long";
   ...> }
$29 ==> "HELO"

case句のタイプパターンで定義した変数のスコープは、case句の中だけです。
ここではswitchの外にあるのと同じく変数sを定義してcase句で上書きしていますが、switchの外に影響はありません。

jshell> s = "helo"
s ==> "helo"

jshell> switch (s) {
   ...>     case String s when s.length() < 5 -> (s="Hello").toUpperCase();
   ...>     default -> "too long";
   ...> }
$30 ==> "HELLO"

jshell> s
s ==> "helo"

Record Patternsと併用が本命ですね。

return switch(n) {
    case IntExpr(int i) -> i;
    case NegExpr(Expr n) -> -eval(n);
    case AddExpr(Expr left, Expr right) -> eval(left) + eval(right);
    case MulExpr(Expr left, Expr right) -> eval(left) * eval(right);
    default -> throw new IllegalArgumentException(n);
};

ここでSealed Classと組み合わせて次のような定義になっているとします。

sealed interface Expression
  permits IntExpr, NegExpr, AddExpr, MulExpr {}
record IntExpr(int i) implements Expression {}
record NegExpr(Expr n) implements Expression {}
record AddExpr(Expr left, Expr right) implements Expression {}
record MulExpr(Expr left, Expr right) implements Expression {}

そうすると、Expressionが取りうるのが4つのレコードで全てということがわかるのでdefault句が不要になります。また、全てをチェックしていることを保証できるのが、既存のコードと違うところで、パターンマッチのメリットです。

Expression n = getExp();
return switch(n) {
    case IntExpr(int i) -> i;
    case NegExpr(Expr n) -> -eval(n);
    case AddExpr(Expr left, Expr right) -> eval(left) + eval(right);
    case MulExpr(Expr left, Expr right) -> eval(left) * eval(right);
};

API

JEP 429: Scoped Values (Incubator)
JEP 434: Foreign Function & Memory API (Second Preview)
JEP 436: Virtual Threads (Second Preview)
JEP 437: Structured Concurrency (Second Incubator)
JEP 438: Vector API (Fifth Incubator)

小さいもの

URLのコンストラクタがDeprecatedに

かわりにURIからtoURL()を使います。

jshell> URL u = URI.create("http://example.com").toURL()
u ==> http://example.com

これは、URLの場合にスペースが入ったURLでもインスタンス化できてしまうからのようです。

jshell> new URL("http://example.com/hello world")
$7 ==> http://example.com/hello world

URIの場合はスペースが入ったURLを指定すると例外が発生します。

jshell> URL u = URI.create("http://example.com/hello world").toURL()
|  例外java.lang.IllegalArgumentException: Illegal character in path at index 24: http://example.com/hello world

16ビット浮動小数点数への対応

floatを16ビット浮動小数点数に変換するメソッド、short floatToFloat16(float)float float16ToFloat(short)が導入されました。計算用のメソッドは用意されてないようです。

jshell> Float.floatToFloat16((float)3.14)
$11 ==> 16968

jshell> Float.float16ToFloat((short)16968)
$12 ==> 3.140625

jshell> Float.floatToFloat16(Float.NaN)
$13 ==> 32256

jshell> Float.float16ToFloat((short)32256)
$14 ==> NaN

JEP 429: Scoped Values (Incubator)

今回唯一の新入りです。
同じスレッド内で値を共有したいときThreadLocalを使いますが、値の変更が可能であったり子スレッドに値が引き継がれたり少し重いので、より限定された仕組みを提供する、ということのようです。
つまり、値を引数でひっぱりまわすのは面倒なのでグローバル変数的にstaticフィールドを使いたい程度のモチベーションで値を共有化するときに、スレッドセーフのためのThreadLocalは重すぎる、という感じですね。
たとえば次のような処理があります。

void start() {
  proc1("test");
}

void proc1(String str) {
  System.out.println(str);
  proc2(str);
}

void proc2(String str) {
  System.out.println(str);
}

これを、全部のメソッドにいちいち引数を設定して値をひきまわるのは面倒なのでフィールドを使おう、という場合。

String str;

void start() {
  str = "test";
  proc1();
}

void proc1() {
  System.out.println(str);
  proc2();
}

void proc2() {
  System.out.println(str);
}

これは複数スレッドから呼び出されると正しく動かないことがあります。
スレッドセーフにするためにThreadLocalを使っていました。

final ThreadLocal<String> STR = new ThreadLocal<>();

void start() {
  STR.set("test");
  proc1();
}

void proc1() {
  System.out.println(STR.get());
  proc2();
}

void proc2() {
  System.out.println(STR.get());
}

しかし、引数を書くのめんどいね、くらいのモチベーションで使うにはThreadLocalは重過ぎるので、軽量な値共有手段としてScopedValueが導入されます。

final ScopedValue<String> STR = new ScopedValue<>();

void start() {
  ScopedValue.where(STR, "test")
    .run(() -> proc1());
}

void proc1() {
  System.out.println(STR.get());
  proc2();
}

void proc2() {
  System.out.println(STR.get());
}

JEP 436: Virtual Threads (Second Preview)

仮想スレッドです。
2ndプレビューになりました。Java 19からの変更はありません。Java 21で正式導入されそうです。

いままでJavaは、初期にプラットフォームスレッドを使うようになって以来、ずっとThreadとしてプラットフォームスレッドを使ってきました。
プラットフォームスレッドというのは、OSが管理するようなスレッドです。OSが管理するスレッドというのは、YouTubeで動画を見てTwitterして時計を表示してファイナルファンタジーやって、と何でも同時に動かせるように設計されています。
もちろんアプリケーションの中でいろいろな働きのスレッドを動かすのですが、多くのスレッドを立ち上げることになるのは、結局同じことをするスレッドです。
その中で、計算のために同じことをするというのはハードウェアの構成を考えないといけなくて、そのための構成がGPUだったりVector APIで扱うSIMD命令だったりするわけですね。
そして、アプリケーションの処理としては結局のところ計算の時間よりも通信待ちの時間のほうがはるかに長いということで、通信待ち時間を有効利用したいというのが並列処理の主な目的になっていました。
そうすると、計算の最中にバランスとりながら処理を切り替えるプラットフォームスレッドというのはオーバークオリティだったわけで、やりたいことに対してメモリも食うし限界も低いし遅いということになってたわけです。
ということで、現状でJavaではReactiveやRxといって通信のときに他の処理に制御を渡せるようなコードを書いているのですが、これが非常に難しくてやってられないということで、Javaから離れてKotlinのように自然にそういう処理が書ける言語に移行する人がでています。

という問題に対処するために、OSではなくJVMが管理するスレッドがVirtual Threadとして導入されます。Virtual ThreadはProject Loomで開発されていました。

Virtual Threadは既存のプラットフォームスレッドと同様に扱うことができるよう気をつけてAPIが設計されています。

ただ、プラットフォームスレッドではnew Thread()としてスレッドを生成できましたが、コンストラクタでVirtual Threadを生成することはできません。

いままでのプラットフォームスレッドはこんな感じで使いました。

jshell> Thread t = new Thread(() -> System.out.println("hello"))
t ==> Thread[#220496,Thread-110304,5,main]

jshell> t.getState()
$33 ==> NEW

jshell> t.start()

jshell> hello

jshell> t.getState()
$35 ==> TERMINATED

Java 19ではThread.Builderが用意されて、ofPlatformofVirtualでプラットフォームスレッドと仮想スレッドを分けれるようになります。
unstartedメソッドを使うと、new Threadと同様にまだ開始していないスレッドを得れます。

jshell> Thread t = Thread.ofPlatform().unstarted(() -> System.out.println("hello"))
t ==> Thread[#220491,Thread-110303,5,main]

jshell> t.start()

hello
jshell> t.getState()
$24 ==> TERMINATED

そしてofVirtualに変更すると仮想スレッドを得れます。仮想スレッドはThreadクラスのオブジェクトとして扱えます。使い方はプラットフォームスレッドと同様です。

jshell> Thread t = Thread.ofVirtual().unstarted(() -> System.out.println("hello"))
t ==> VirtualThread[#220492]/new

jshell> t.start()

jshell> hello

jshell> t.getState()
$27 ==> TERMINATED

startメソッドを使うと、スレッドの実行とThreadオブジェクトの取得が同時に行えます。

jshell> Thread t = Thread.ofVirtual().start(() -> System.out.println("hello"))
t ==> VirtualThread[#220497]/runnable
hello

jshell> t.getState()
$37 ==> TERMINATED

ExecutorServiceで仮想スレッドを使う場合は、Executors.newVirtualThreadPerTaskExecutorを使います。

jshell> ExecutorService ex = Executors.newVirtualThreadPerTaskExecutor()
ex ==> java.util.concurrent.ThreadPerTaskExecutor@28ba21f3
jshell> ex.submit(() -> System.out.println("hello"))
$40 ==> java.util.concurrent.ThreadPerTaskExecutor$ThreadBoundFuture@64a294a6[Not completed]
hello

jshell> ex.isTerminated()
$41 ==> false

jshell> ex.isShutdown()
$42 ==> false

jshell> ex.shutdown()
jshell> ex.isTerminated()
$44 ==> true

jshell> ex.isShutdown()
$45 ==> true

では、少し性能を見てみましょう。
3秒待つメソッドを定義しておきます。

jshell> import java.time.Duration

jshell> void s(){ try{ Thread.sleep(Duration.ofSeconds(3));} catch(Exception ex){}}
|  次を作成しました: メソッド s()

プラットフォームスレッドを100起動してそれぞれで3秒待ちます。

jshell> IntStream.range(0, 100).forEach(i -> Thread.ofPlatform().start(() -> s()))

スレッドが100増えてすぐ元にもどっていることがわかります。
image.png

仮想スレッドを100起動してそれぞれで3秒待ちます。

jshell> IntStream.range(0, 100).forEach(i -> Thread.ofVirtual().start(() -> s()))

そうすると、17スレッドだけ増えて31スレッドになります。

image.png

100増えないことと、終わってもスレッドが残っていることがわかります。

それでは10万スレッドを同時に実行してみましょう。
まずは仮想スレッドから。

jshell> IntStream.range(0, 100_000).forEach(i -> Thread.ofVirtual().start(() -> s()))

10:09のところで実行しています。スレッド数は31のまま、CPU使用率は7%くらいです。メモリは120MBくらい使っていますね。

image.png

プラットフォームスレッドで試してみます。

jshell> IntStream.range(0, 100_000).forEach(i -> Thread.ofPlatform().start(() -> s()))

まず、しばらく処理が終わりません。
スレッドは2万弱くらいまで使われていますが、その2万弱のスレッドでスレッドが終わったら別のスレッドというふうにやりくりして10万スレッドを処理できるまで、繰り返されます。
また、CPUは80%近くまで使われています。処理はSleepするだけなので、スレッド切り替えにCPUを使っていると推測されます。メモリはそれほど使っていませんね。

image.png

このように、多数のスレッドを処理するときにプラットフォームスレッドでは同時処理数に限界があることと、CPUパワーを余分に使ってしまうので、軽量な仮想スレッドが求められ実装されたわけです。

JEP 437: Structured Concurrency (Second Incubator)

並列処理では、複数の処理を実行するときに、両方が終われば正常終了とか、どちらか片方が終われば終了だとか、どちらか一方でも例外が発生したら終了だとか、同時に行う処理で連動することがあります。
しかし、これを既存のjoinwaitなどで制御しようとすると、実際にはjoinからwaitへのGo Toを書くようなコードになって、処理が追えなくなります。
そこで導入されるのが構造化並列性といいます。
こんな感じ。詳しくはあとで書きます!

Response handle() throws ExecutionException, InterruptedException {
    try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
        Future<String>  user  = scope.fork(() -> findUser());
        Future<Integer> order = scope.fork(() -> fetchOrder());

        scope.join();           // Join both forks
        scope.throwIfFailed();  // ... and propagate errors

        // Here, both forks have succeeded, so compose their results
        return new Response(user.resultNow(), order.resultNow());
    }
}

JEP 438: Vector API (Fifth Incubator)

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

使うためには実行時やコンパイル時に--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 434: Foreign Function & Memory API (Second Preview)

2nd Previewになりました。

Foreign Memory API

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

ということで、ヒープ外のメモリを直接扱うAPIがJava 14でインキュベータモジュールとして導入され、今回6バージョン目のインキュベータです。
次のようなコードになります。

import java.lang.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);
   }
}

コンパイル、実行では--enable-previewを付ける必要があります。

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

jshell> import java.lang.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 java.lang.foreign.*;

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

Foreign Function API

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

Java 16でForeign Linkerとして1stインキュベータになり、今回4バージョン目のインキュベータです。

たとえばこんな感じのC関数があって。

size_t strlen(const char *s);

こんな感じでMethodHandleを取り出して。

CLinker linker = CLinker.systemCLinker();
MethodHandle strlen = linker.downcallHandle(
    linker.lookup("strlen").get(),
    FunctionDescriptor.of(JAVA_LONG, ADDRESS)
);

こんな感じで呼び出すようです。

MemorySegment cString = implicitAllocator().allocateUtf8String("Hello");
long len          = strlen.invoke(cString);  // 5

JDK

JDKの変更、つまりリリース方針などの変更はありません。

90
60
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
90
60