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がダウンロード可能です)
- Oracle JDK ※対外サーバーの運用には有償ライセンスが必要です
- Adoptium Temurin
- Azul Zulu
- Liberica JDK
- Amazon 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"
ただ、なんかバグってる可能性
なんかJava 20のjavacかjavaがバグってる気がする#java20 pic.twitter.com/zyTQLfRBV4
— きしだൠ(K1S) (@kis) March 22, 2023
ネストした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
が必要です。
「といってもパターンマッチってそんなに出番ないのでは?」
と思うかもしれませんが、大きな影響が。
switch
でcase 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"
default
とnull
で同じ処理したいときにアロースタイルで書けないんでは?という疑問に応えて、default
がcase
に書けるようになっています。
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が用意されて、ofPlatform
かofVirtual
でプラットフォームスレッドと仮想スレッドを分けれるようになります。
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増えてすぐ元にもどっていることがわかります。
仮想スレッドを100起動してそれぞれで3秒待ちます。
jshell> IntStream.range(0, 100).forEach(i -> Thread.ofVirtual().start(() -> s()))
そうすると、17スレッドだけ増えて31スレッドになります。
100増えないことと、終わってもスレッドが残っていることがわかります。
それでは10万スレッドを同時に実行してみましょう。
まずは仮想スレッドから。
jshell> IntStream.range(0, 100_000).forEach(i -> Thread.ofVirtual().start(() -> s()))
10:09のところで実行しています。スレッド数は31のまま、CPU使用率は7%くらいです。メモリは120MBくらい使っていますね。
プラットフォームスレッドで試してみます。
jshell> IntStream.range(0, 100_000).forEach(i -> Thread.ofPlatform().start(() -> s()))
まず、しばらく処理が終わりません。
スレッドは2万弱くらいまで使われていますが、その2万弱のスレッドでスレッドが終わったら別のスレッドというふうにやりくりして10万スレッドを処理できるまで、繰り返されます。
また、CPUは80%近くまで使われています。処理はSleepするだけなので、スレッド切り替えにCPUを使っていると推測されます。メモリはそれほど使っていませんね。
このように、多数のスレッドを処理するときにプラットフォームスレッドでは同時処理数に限界があることと、CPUパワーを余分に使ってしまうので、軽量な仮想スレッドが求められ実装されたわけです。
JEP 437: Structured Concurrency (Second Incubator)
並列処理では、複数の処理を実行するときに、両方が終われば正常終了とか、どちらか片方が終われば終了だとか、どちらか一方でも例外が発生したら終了だとか、同時に行う処理で連動することがあります。
しかし、これを既存のjoin
やwait
などで制御しようとすると、実際には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の変更、つまりリリース方針などの変更はありません。