すでに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)
Preview機能としてSwitch文が拡張されて、式として使えるようになりました。(Preview機能を使うにはjavac
などに--enable-preview
をつける必要があります)
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 = "休日";
}
Rule Switch
->
を使うことで、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;}
| ^--------------------------^
値を返すbreak
は正式仕様になる際にbreak-with
になる可能性がありました。
Call for feedback -- enhanced switch
しかし、JDK 13ではyield
になっています。
341: Default CDS Archives
クラスデータをあらかじめ作成して共有するという仕組みがあるのですが、いままでは java -Xshare:dump
として一旦自分でクラスデータを作っておく必要がありました。
JDK12からはlib/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でもZGCとEpsillonという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
インタフェースなどが追加されました。
そしてString
やInteger
、Class
やEnum
など定数として使うなーというクラスが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.NumberFormat
のgetCompactNumberInstance
メソッドで取得します。
日本ロケールの場合はこんな感じ。兆まで対応してて京は未対応な感じですね。
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兆"
ロケールを指定する場合は、スタイルも指定する必要があります。SHORT
かLONG
かが選べます。
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だったようです。
小数点以下を表示したいときはsetMaximumFractionDigit
やsetMinimumFractionDigit
を使います。
デフォルトでは小数点以下は四捨五入されます。
jshell> cnf.format(123456)
$20 ==> "123 thousand"
setMaximumFractionDigits
を使うと小数点以下の桁数の最大を指定できます。
jshell> cnf.setMaximumFractionDigits(2)
jshell> cnf.format(123456)
$21 ==> "123.46 thousand"
jshell> cnf.format(123400)
$22 ==> "123.4 thousand"
setMinimumFractionDigits
を使えば小数点以下の桁数の最小を指定できます。
jshell> cnf.setMinimumFractionDigits(2)
jshell> cnf.format(123400)
$23 ==> "123.40 thousand"
String.indent(int)
指定した文字数分のインデントを付け加えます。
jshell> "123\n456".indent(2)
$24 ==> " 123\n 456\n"
負の値を指定すると、インデントを減らすことができます。
jshell> " 123\n 123".indent(-2)
$25 ==> "123\n123\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
に例外処理をするためのメソッドexceptionallyAsync
、exceptionallyCompose
、exceptionallyComposeAsync
が追加されています。実際には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