Java
JDK12
java12

Java12新機能まとめ

すでにJava12はRampdownフェーズに入り、機能がほぼ確定しており、順調に行けば3月19日にリリースされます。

ということで、Java12に入る機能をJEP、API、その他にわけてまとめます。

JDK 12 Early-Access Builds


JEP

まずは大きな機能をまとめたJEPベースの変更

http://openjdk.java.net/projects/jdk/12/

全部で8個のJEPが入ってます。ふつうのJavaプログラマに影響ありそうなのは上の3つとDefault CDSですかね。残念ながらRaw String Literalsはドロップされました。

189: Shenandoah: A Low-Pause-Time Garbage Collector (Experimental)

230: Microbenchmark Suite

325: Switch Expressions (Preview)

334: JVM Constants API

340: One AArch64 Port, Not Two

341: Default CDS Archives

344: Abortable Mixed Collections for G1

346: Promptly Return Unused Committed Memory from G1

それぞれについて、使いそう順に書いていきます。


325: Switch Expressions (Preview)

Switch文が拡張されて、式として使えるようになりました。

String time = switch (weekday) {

case MONDAY, FRIDAY -> "10:00-18:00";
case TUESDAY, THURSDAY -> "10:00-14:00";
default -> "休日";
};

これは今までだとこんな感じになりますね。

String time;

switch (weekday) {
case MONDAY:
case FRIDAY:
time = "10:00-18:00";
break;
case TUESDAY:
case THURSDAY:
time = "10:00-14:00";
break;
default:
time = "休日";
}

switchを使う多くの場合がこのようなパターンだったと思います。いままでのswitchでは、記述量が多いのはもちろんですが、break抜けや間違った変数を更新してしまう、更新の抜けがないことが保証されないといったバグの原因になっていました。これが解消されることになるため、積極的に使っていきたいところです。

実際には3つの仕様追加に分かれています。


複数case

caseに複数の値を指定できるようになりました。

String time;

switch (weekday) {
case MONDAY, FRIDAY:
time = "10:00-18:00";
break;
case TUESDAY, THURSDAY:
time = "10:00-14:00";
break;
default:
time = "休日";
}


アロー構文

->を使うことで、breakが不要になりました。

String time;

switch (weekday) {
case MONDAY, FRIDAY -> time = "10:00-18:00";
case TUESDAY, THURSDAY -> time = "10:00-14:00";
default -> time = "休日";
}

ブロックを使えば複数行の処理も行えます。

String time;

switch (weekday) {
case MONDAY, FRIDAY -> {
var endTime = getEndTime();
time = "10:00-" + endTime;
}
case TUESDAY, THURSDAY -> time = "10:00-14:00";
default -> time = "休日";
}

これは積極的に使いたいですね。


Switch式

そして、switchが式にもなりました。

アロー構文を使わない場合やアロー構文にブロックを使った場合は、breakで値を返します。

String time = switch (weekday) {

case MONDAY, FRIDAY -> {
var endTime = getEndTime();
break "10:00-" + endTime;
}
case TUESDAY, THURSDAY -> "10:00-14:00";
default -> "休日";
};

すべての入力に対応できない場合はエラーになります。

jshell> switch("a"){ case "a" -> 3;}

| エラー:
| switch式がすべての可能な入力値をカバーしていません
| switch("a"){ case "a" -> 3;}
| ^--------------------------^


341: Default CDS Archives

クラスデータをあらかじめ作成して共有するという仕組みがあるのですが、いままでは java -Xshare:dumpとして一旦自分でクラスデータを作っておく必要がありました。

JDK12からはbin/server/classes.jsaがあらかじめ含まれていて、この作業が不要になり、またJDK11で-Xshare:autoがデフォルトになっていてclasses.jsaがあれば自動でCDSが使われるようになってるので、なにもしなくても最初からCDSが使われるようです。

これで、Javaの起動時間が改善するはず。


189: Shenandoah: A Low-Pause-Time Garbage Collector (Experimental)

Red Hatが開発しているGCです。2GBでも200GBでも停止時間が変わらない、というのがウリっぽい。

G1GCと共通のコードが多いということで、Shenandoahの開発時にG1GCも改善できてしまったということが結構あるようです。

今回のJEP346も、そんなShenandoah開発中の改善をG1GCに反映させたもののひとつです。

Java10でのParallel Full GC for G1もそうみたい。

InfoQ:レッドハットでのOpenJDKの未来

さて、JDK11でもZGCEpsillonという2つのGCが入ってます。

ということで、JDK12には7つのGCが入ってるわけですね。


  • Serial GC

  • Parallel GC

  • Concurrent Mark and Sweep (CMS)

  • Garbage First GC (G1GC)

  • Epsillon GC

  • ZGC

  • Shenandoah

デフォルトはJava9からG1GCになっています。

JEP 248: Make G1 the Default Garbage Collector

ただし、メモリが少ない環境ではParallel GCが、シングルコア環境ではSerial GCが使われるようです。

ZGCは32GB以下のヒープでは使わないほうがよさそうなので、数百GBから数TBのヒープがあるときに使う感じだと思います。

G1GCとShenandoahの使い分けはどうなんでしょうね?

CMSはJava9から非推奨になっていて、将来的には削除される予定

JEP 291: Deprecate the Concurrent Mark Sweep (CMS) Garbage Collector

あと、サーバレスみたいにリクエストごとに起動するような場合はEpsilon GCがいいんでは、と思ったんだけど、試しに簡単なプログラムでEpsilon GC使ってみたところ遅くなってますね。


230: Microbenchmark Suite

JMH(Java Microbenchmark Harness?)というベンチマークフレームワークがあるのですが、JDKに含まれるようになったようです。

OpenJDK: jmh

ただ、外部のjmhを利用するのに比べて何がかわったか、どうやって使うかは、資料をみつけれてません。


334: JVM Constants API

Java11でDynamic Class-File Constantsが入って、クラス初期化時の処理にInvokeDynamicが使えるようになりました。

JEP 309: Dynamic Class-File Constants

けれども、このときは言語仕様やAPIは追加されなかったので、利用するには自分でクラスファイルを作成する必要がありました。

java.lang.constantパッケージが追加されて、ConstableインタフェースやConstantDescインタフェースなどが追加されました。

そしてStringIntegerClassEnumなど定数として使うなーというクラスがConstableを実装してdescribeConstable()メソッドを実装しています。


346: Promptly Return Unused Committed Memory from G1

未使用のメモリをアイドル時に返却する。

Shenandoahでの変更のG1GCへの反映ぽい。


344: Abortable Mixed Collections for G1

Mixed GCを停止可能にする。

これがShenandoah由来かどうかは知らない。


340: One AArch64 Port, Not Two

ARM64bit用のPortとして、arm64とaarch64のふたつのソースコードがあったので、arm64のほうを消して作業の重複などをなくしたということらしい。


API

APIの変更も派手なのはなく、ちょっとした修正がいくつか入ってる感じ。


CompactNumberFormat

大きな数値を、たとえば10,000を1万とか10Kとかで表記するNumberFormatです。

[JDK-8188147] Compact Number Formatting support - Java Bug System

インスタンスはjava.text.NumberFormatgetCompactNumberInstanceメソッドで取得します。

日本ロケールの場合はこんな感じ。兆まで対応してて京は未対応な感じですね。

jshell> import java.text.*

jshell> var cnf = NumberFormat.getCompactNumberInstance()
cnf ==> java.text.CompactNumberFormat@73cf7357

jshell> cnf.format(10000)
$4 ==> "1万"

jshell> cnf.format(10000_0000)
$5 ==> "1億"

jshell> cnf.format(10000_0000_0000L)
$6 ==> "1兆"

jshell> cnf.format(10000_0000_0000_0000L)
$7 ==> "10000兆"

ロケールを指定する場合は、スタイルも指定する必要があります。SHORTLONGかが選べます。

SHORTだとこんな感じ。

jshell> cnf = NumberFormat.getCompactNumberInstance(Locale.US, NumberFormat.Style.SHORT)

cnf ==> java.text.CompactNumberFormat@952071d5

jshell> cnf.format(1000)
$9 ==> "1K"

jshell> cnf.format(1000_000)
$10 ==> "1M"

jshell> cnf.format(1000_000_000)
$11 ==> "1B"

jshell> cnf.format(1000_000_000_000L)
$12 ==> "1T"

jshell> cnf.format(1000_000_000_000_000L)
$13 ==> "1000T"

Tまで対応しています。あれ10億は1Gじゃないの?という感じですね。

ではLONGを指定してみましょう。

jshell> cnf = NumberFormat.getCompactNumberInstance(Locale.US, NumberFormat.Style.LONG)

cnf ==> java.text.CompactNumberFormat@38c39d6b

jshell> cnf.format(1000)
$15 ==> "1 thousand"

jshell> cnf.format(1000000)
$16 ==> "1 million"

jshell> cnf.format(1000000000)
$17 ==> "1 billion"

jshell> cnf.format(1000000000000L)
$18 ==> "1 trillion"

jshell> cnf.format(1000000000000000L)
$19 ==> "1000 trillion"

1000の場合はKiloのKだったようですが、MやTはMegaやTeraではなくMillionやTrillionだったようです。


String.indent(int)

指定した文字数分のインデントを付け加えます。

jshell> "123\n456".indent(2)

$21 ==> " 123\n 456\n"

Raw String Literalsの名残ですね。

[JDK-8200435] String::align, String::indent - Java Bug System

同じくRSLのために入ったalign()はea27には残ってますが、削除コミットがあったのでea28では消えるはずです。

[JDK-8215490] Remove String::align - Java Bug System


String.transform(Function)

これもRaw String Literalsの名残ですね。

Stringを受け取って何かを返す関数があるときに、それを適用させるものです。

ただ、これだけでは普通に関数呼び出せばいいんではという感じなんですけど、モチベーションとしては考える順に書けるという感じですね。

[JDK-8203703] String::transform - Java Bug System

たとえば、名前を渡せば住所が得れる関数と、住所を渡せばそこの人口が得れる関数があるとします。

ここではMapを。

jshell> var addresses = Map.of("Mike", "Fukuoka", "John", "Tokyo")

addresses ==> {John=Tokyo, Mike=Fukuoka}

jshell> var population = Map.of("Tokyo", 30000000, "Fukuoka", 2000000)
population ==> {Fukuoka=2000000, Tokyo=30000000}

そして、「名前を渡したらその人の住所の人口を表示したい」というとき、こんな感じに書く必要がありました。

jshell> population.get(addresses.get(name))

$37 ==> 2000000

「名前があって住所をとって人口をとる」という処理なのに「人口を返すのに住所を渡すのに名前を渡す」の順に書く必要があります。

これが、transformを使うと次のように書けます。

jshell> name.transform(addresses::get).transform(population::get)

$38 ==> 2000000

「名前で住所とって人口とる」という順になってますね。

言語仕様で関数(値)値→関数の順に書けるようになってる言語も多いですけど、とりあえずJavaでそれを実現した、って感じですね。

Javaでもname→address::get→population::getの順に書けるようになればいいのに。


Collectors.teeing(Collector, Collector, BiFunction)

ふたつのCollectorの結果を結び付けます。

[JDK-8209685] Create Collector which merges results of two other collectors - Java Bug System

たとえば、文字列のリストがあって、空文字列を省いてカンマ区切りにしつつ最終的な要素数も得たい、みたいなときには一回のStream処理では解決できませんでしたが、そういうことができるようになります。

jshell> Stream.of("aaa", "", "bbb", "ccc").

...> filter(Predicate.not(String::isEmpty)).
...> collect(Collectors.teeing(
...> Collectors.joining(","),
...> Collectors.counting(),
...> Map::entry))
$39 ==> aaa,bbb,ccc=3


Files.mismatch(Path, Path)

ふたつのファイルのうち、最初に異なった位置を返します。同じファイルなら-1

isSameFileはありますが、どこが違うかも欲しいんじゃないの?ということで追加されたようです。

[JDK-8202302] (fs) New Files.mismatch method for comparing files - Java Bug System


InputStream.skipNBytes(long)

指定したバイト数、データをスキップします。

[JDK-8214072] InputStream.skipNBytes(long k) to skip exactly k bytes - Java Bug System

skip(long)があるじゃん、という気がしますが、skipは実際に進めた数を返すのに対して、skipNBytesは戻り値を返さず、ストリームの終わりを超える値を指定するとEOFExceptionを投げます。

jshell> var input = new ByteArrayInputStream(new byte[5])

input ==> java.io.ByteArrayInputStream@64bf3bbf

jshell> input.skip(2)
$25 ==> 2

jshell> input.skip(4)
$26 ==> 3

jshell> input.reset()

jshell> input.skipNBytes(2)

jshell> input.skipNBytes(4)
| 例外java.io.EOFException
| at InputStream.skipNBytes (InputStream.java:600)
| at (#29:1)


CompletableFuture.exceptionallyAsync(Function)

CompletableFutureに例外処理をするためのメソッドexceptionallyAsyncexceptionallyComposeexceptionallyComposeAsyncが追加されています。実際にはCompletableStageインタフェースに追加されたので実装した、という感じ。

[JDK-8211010] Add exception handling methods to CompletionStage and CompletableFuture - Java Bug System

すでにexceptionallyがあるけど、compose、async版を追加した、と。


str.equals("")をstr.isEmpty()に

ぼくでもできる系の変更。こういうリファクタリングもよく入ってます。

[JDK-8214971] Replace use of string.equals("") with isEmpty() - Java Bug System


その他


Java Flight Recorderツール

jfrコマンドが追加されています。

$ jfr print --categories GC --events CPULoad recording.jfr

などとして使うっぽい

[JDK-8205517] JFR tool - Java Bug System