2019/03/16

JEPでは語れないJava SE 12

このエントリーをはてなブックマークに追加

アメリカ西海岸時間の3月19日にJava SE 12がリリースされます。

恒例となっているJEPで提案されている以外のJava SEの変更をまとめておきましょう。

全般的にいえるのが、Java SE 12の変更がかなり小規模だということ。言語仕様的にはswitchが文から式になったことが大きいですが、APIの変更はほんとにちょっとしかありません。

今回もABC順にならんでいます。同じように、セキュリティ系のAPIはちゃんと理解していないので、省略してます。

 

廃止になったAPI

Java SE 12では5つのメソッドが廃止になっています。

いずれも、Java SE 10で@DeprecatedのforRemovalがtrueになったメソッドです。つまり、forRemovalがtrueになってから1年で廃止ということです。

  • java.io.FileInputStream.finalize()
  • java.io.FileOutputStream.finalize()
  • java.util.zip.Deflater.finalize()
  • java.util.zip.Inflater.finalize()
  • java.util.zip.ZipFile.finalize()

finalizeメソッドですから、なくても何ら問題はないはず。というか、逆にfinalizeメソッドを使っていたとしたら、その設計の方が問題です。

 

これら以外に、4つのクラスの4メソッドが廃止されました。

いずれもスーパークラスをオーバーライドしていたメソッドで、スーパークラスのメソッドを使うようになったというだけです。

  • java.lang.FileNotFoundException.getCause()
  • java.lang.ExceptionInInitializerError.getCause()
  • java.lang.reflext.UndeclaredThrowableException.getCause()
  • java.security.PrivilegedActionException.getCause()

 

廃止予定のAPI

Java SE 12での廃止予定APIの追加はありませんでした。

 

追加されたAPI

java.lang.constantパッケージ

なんと、java.baseモジュールにパッケージが追加されました!

とはいうものの、普通の開発者がjava.lang.constantパッケージを使うことはまずないと思います。というのも、Java SE 11で導入されたindyの定数版であるcondyで使われるためのクラス群だからです。

このパッケージはJEP 334: JVM Constants APIに基づくパッケージです。

また、このパッケージの追加にともない、java.lang.invokeパッケージのクラス群にもクラスの追加やメソッドの追加があります。

追加されたクラス群は

  • TypeDescriptorインタフェース
  • TypeDescriptor.OfFieldインタフェース
  • TypeDescriptor.OfMethodインタフェース
  • VarHandle.VarHandleDescクラス

の4つです。

メソッドの追加は3つ。

  • Optional<MethodHandleDesc> MethodHandle.describeConstable()
  • String MethodType.describeString()
  • Optional<MethodTypeDesc> describeConstable()
さらに、java.lang.Classクラスにも以下のメソッドが追加されています。
  • Class<?> arrayType()
  • Class<?> componentType()
  • Optional<ClassDesc> describeConstable()
  • String descriptorString()

他にも、EnumクラスがConstableインタフェースを実装するようになり、サブクラスとしてEnum.EnumDescクラスが追加されています。

また、Integerクラス、Longクラス、Floatクラス、Doubleクラス、StringクラスがConstableインタフェースとConstatntDescインタフェースを実装しています。

これらのクラスはdescribeConstantメソッドとresolveConstantDescメソッドが追加されています。

いずれも、普通の開発では使わないと思います。

java.ioパッケージ

InputStreamクラス

InputStreamクラスにはskipメソッドがありましたが、それに関連したメソッドが追加されました。

  • void skipNBytes(long n)

skipメソッドは "さまざまな理由から、skipメソッドは指定よりも少ないバイト数しかスキップしないことがあります" とJavadocに書いてあります。このため、返り値として実際にスキップしたバイト数が返ります。

これに対し、skipNBytesメソッドは必ず指定したバイト数をスキップします。このため、返り値はありません。

 

java.langパッケージ

java.lang.Classクラスのメソッド追加に関しては上述したので、それ以外の追加分です。

Character.UnicodeBloch/UnicodeScriptクラス

JEPにはなっていないのですが、Java SE 12はUnicode 11.0をサポートしています。Java SE 11はUnicode 10.0でした。

この変更に伴って、ブロックとスクリプトの定数が追加されています。たとえば、チェスの駒の絵文字を表すブロックとしてCHESS_SYMBOLSなどがあります。

 

Stringクラス

Stringクラスは前述したようにcondy関連のメソッドが2つ追加されていますが、それ以外にもう1つメソッドが追加されました。

  • String indent(int n)

indentメソッドは指定した数だけ行頭に空白を埋め込む処理を行います。さらに、文字列の最後に\nが挿入されます。

jshell> var text = "Hello\nWorld!"
text ==> "Hello\nWorld!"

jshell> System.out.println(text);
Hello
World!

jshell> text = text.indent(4);
text ==> "    Hello\n    World!\n"

jshell> System.out.println(text)
    Hello
    World!


jshell>

なお、負の数を指定すると、行頭のホワイトスペースが指定した数だけ削除されます。削除されるのはホワイトスペースだけです。

jshell> text = text.indent(-2)
text ==> "  Hello\n  World!\n"

jshell> text = text.indent(-3)
text ==> "Hello\nWorld!\n"

jshell>

もともとJava SE 12はJEP 326 Raw String Literalsが導入される予定だったのですが、結局スリップしてしまいました。

このJEP 326にともなって、Stringクラスにはalignメソッドなどのメソッドが追加予定だったのです。しかし、JEP 326がスリップしてしまったことにより、これらのメソッドも追加されないことになっていました。

ところが、indentメソッドだけは生き残ったというわけです。

 

java.netパッケージ

SecureCacheResponseクラス

SecureCacheResponseクラスにはSSLセッションを取得するメソッドが追加されました。

  • Optional<SSLSession> getSSLSession()

 

ServerSocketクラス

ちょっと珍しいのですが、コンストラクタが追加されました。

  • protected ServerSocket(SocketImpl impl)

追加されたといっても、protectedメソッドなので普通は使わないでしょう。

 

java.nio.fileパッケージ

Filesクラス

2つのファイルの相違点を見つけるメソッドが追加されました。

  • static long mismatch(Path path, Path path2)

引数のpathとpath2のファイルの相違点を見つけて、はじめの相違点までのバイト数を返します。

相違点がない場合、-1が返ります。

C:\demo>cat hello1.txt
Hello, World!

C:\demo>cat hello2.txt
Hello, Java!

C:\demo>cat hello3.txt
Hello, World!

C:\demo>jshell
|  JShellへようこそ -- バージョン12
|  概要については、次を入力してください: /help intro

jshell> import java.nio.file.*

jshell> var hello1 = Paths.get("hello1.txt")
hello1 ==> hello1.txt

jshell> var hello2 = Paths.get("hello2.txt")
hello2 ==> hello2.txt

jshell> var hello3 = Paths.get("hello3.txt")
hello3 ==> hello3.txt

jshell> Files.mismatch(hello1, hello2)
$5 ==> 7

jshell> Files.mismatch(hello1, hello3)
$6 ==> -1

jshel>

 

java.textパッケージ

CompactNumberFormatクラス

数字の10,000を10Kとか1万とか記述することがありますね。ところが、今までJavaでは数字をこのようにフォーマットする標準のAPIがありませんでした。

そこで、Java SE 12で導入されたのがCompactNumberFormatクラスです。

CompactNumberFormatクラスはNumberFormatクラスのサブクラスなので、使い方は一緒です。コンストラクタも用意されていますが、手っ取り早いのはNumberFrmatクラスのファクトリメソッドを使用する方法です。

jshell> import java.text.*

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

jshell> formatter.format(10000)
$3 ==> "1万"

jshell> formatter.format(12345)
$4 ==> "1万"

jshell> formatter.format(15678)
$5 ==> "2万"

jshell>

CompactNumberFormatオブジェクトを取得するためのメソッドがgetCompactNumberInstanceメソッドです。引数なしでコールすると、デフォルトロケールのCompactNumberFormatオブジェクトを返します。

そのため、10,000をフォーマットすると"1万"となるわけです。

それにしても、万の単位より小さいところが四捨五入といいうのは...

四捨五入にするか切り捨てにするかは、NumberFormatクラスのsetRoundingModeメソッドで変更できますが、デフォルトが四捨五入というのはどうなんですかね。

万の次は億なので、100,000,000より大きい数になると億より小さいところも四捨五入です。1億2345万とかにはならないです。

setMaximumFractionDigitsメソッドで四捨五入された部分を小数点で出せますけど、あまり意味ないかも。

ついでに、気をつけなくてはいけないのが、getCompactNumberInstanceメソッドの返り値の型がCompactNumberFormatクラスではなく、NumberFormatクラスだということです。

 

"1万"ではなくて"10K"と表示したい場合は、ロケールを指定します。

jshell> var formatter = NumberFormat.getCompactNumberInstance(new Locale("en", "US"), NumberFormat.Style.SHORT)
formatter ==> java.text.CompactNumberFormat@952071d5

jshell> formatter.format(10000)
$6 ==> "10K"

jshell> formatter.format(12345)
$7 ==> "12K"

jshell>

getCompactNumberInstanceメソッドの第2引数は、新しく追加された列挙型のNumber.Style列挙型です。

Number.Style列挙型の定数としてLONGとSHORTがあります。もちろん、CompactNumberFormatオブジェクトを取得するにはSHORTを指定します。

ロケールがen_USだと、10,000が"10K"とフォーマットされます。こちらもKより小さい桁は切り捨てです。

ところで、SHORTでなくて、LONGにしてみたらどうなるでしょう。

jshell> var formatter = NumberFormat.getCompactNumberInstance(new Locale("en", "US"), NumberFormat.Style.LONG)
formatter ==> java.text.CompactNumberFormat@952071d5

jshell> formatter.format(10000)
$7 ==> "10 thousand"

jshell> formatter.format(12345)
$8 ==> "12 thousand"

jshell>

なんとLONGだとthousandですよw

ちなみに、ロケールをja_JPにして、LONGにしても表示は変わりません。

 

NumberFormatクラス

前述したように、CompactNumberFormatオブジェクトを取得するためのstaticメソッドが追加されました。

  • static NumberFormat getCompactNumberInstance()
  • static NumberFormat getCompactNumberInstance(Locale locale, NumberFormat.Style formatStyle)

これに伴い、java.text.spi.NumberFormatProviderクラスにもgetCompactNumberInstanceメソッドが追加されています。

 

NumberFormat.Fieldクラス

プレフィックスとサフィックスを表す定数が追加されています。

  • static final NumberFormat.Field PREFIX
  • static final NumberFormat.Field SUFFIX

 

NumberFormat.Style列挙型

こちらも前述したようにCompactNumberFormatクラスの生成時に指定するための列挙型です。

LONGとSHORTの定数が定義されています。

 

java.util.concurrentパッケージ

CompletionStageクラス

exceptionallyメソッドの亜種が5種類増えました。

  • default CompletionStage<T> exceptionallyAsync(Function<Throwable, ? extends T> fn)
  • default CompletionStage<T> exceptionallyAsync(Function<Throwable, ? extends T> fn, Executor executor)
  • default CompletionStage<T> exceptionallyCompose (Function<Throwable, ? extends CompletionStage<T>> fn)
  • default CompletionStage<T> exceptionallyComposeAsync(Function<Throwable, ? extends CompletionStage<T>> fn)
  • default CompletionStage<T> exceptionallyComposeAsync(Function<Throwable, ? extends CompletionStage<T>> fn, Executor executor)

exceptionallyメソッドは前段のステージで例外がスローされた時にコールされるメソッドです。前段で例外がスローされていなければ、exceptionallyメソッドはスキップされて、次段のステージが実行されます。

exceptionallyAsyncメソッドは、その名の通りexceptionallyメソッドと同様の処理を非同期に行うメソッドです。

jshell> import java.util.concurrent.*;
  
jshell> CompletableFuture.runAsync(() -> {
   ...>     System.out.println(Thread.currentThread());
   ...>     throw new RuntimeException();
   ...> }).
   ...> exceptionally(ex -> {
   ...>     System.out.println(Thread.currentThread());
   ...>     return null;
   ...> }).get();
Thread[ForkJoinPool.commonPool-worker-3,5,main]
Thread[main,5,main]
$2 ==> null

jshell> CompletableFuture.runAsync(() -> {
   ...>     System.out.println(Thread.currentThread());
   ...>     throw new RuntimeException();
   ...> }).
   ...> exceptionallyAsync(ex -> {
   ...>     System.out.println(Thread.currentThread());
   ...>     return null;
   ...> }).get();
Thread[ForkJoinPool.commonPool-worker-3,5,main]
Thread[ForkJoinPool.commonPool-worker-3,5,main]
$3 ==> null

jshell>

JShellで実行してみるとexceptionallyメソッドはメインスレッドで実行されていますが、exceptionallyAsyncメソッドはForkJoinPoolが管理しているワーカースレッドで実行されていることが分かります。

exceptionallyメソッドの引数はThrowableオブジェクトを受け取って、何らかの値を返すラムダ式です。これに対してexceptionallyComposeメソッドの場合、返り値の型がCompletionStageクラスになっています。

同じようなメソッドにthenComposeメソッドがありますが、それの例外版だと考えればいいと思います。

で、exceptionallyComposeAsyncメソッドはそれの非同期版です。

 

java.util.streamパッケージ

Collectorsクラス

2種類の終端処理を行って、それをまとめるためのメソッドが追加されました。

  • static <T,R1,R2,R> Collector<T,?,R> teeing(Collector<? super T,?,R1> downstream1, Collector<? super T,?,R2> downstream2, BiFunction<? super R1,? super R2,R> merger)

第1引数と第2引数がそれぞれCollectインタフェースのオブジェクト(実際にはCollectorsのstaticメソッドを使用します)、第3引数が2つの終端処理の結果を引数にとるBiFunctionインタフェースのラムダ式です。

たとえば、文字列を連結する処理と、要素数を数える処理を行い、最後に結果をコロンでつなげた文字列にするには、次のように記述します。

jshell> var text = List.of("a", "b", "c", "d", "e")
text ==> [a, b, c, d, e]

jshell> text.stream().
   ...> collect(Collectors.teeing(Collectors.joining(), 
   ...> Collectors.counting(), 
   ...> (x, y) -> x + " : " + y))
$1 ==> "abcde : 5"

jshell>

これはけっこう便利かもしれません。

 

javax.lang.modelパッケージ

SouceVersionクラス

バージョンアップのたびに定数が増えるのはお約束。

  • static final SourceVersion RELEASE_12

また、このクラスもcondy対応でConstableインタフェースを実装するように変更されています。

 

その他

LDAPのサービスプロバイダー用のパッケージjavax.naming.ldap.spiが追加されました。このパッケージにはLdapDnsProviderクラスと、LdapDnsProviderResultクラスが含まれています。

また、javax.net.sslパッケージのHttpsURLConnectionクラスに、getSSLSessionメソッドが追加されています。

最後にSwing。

ファイルチューザー用のjavax.swing.filechooser.FileSystemViewクラスにgetChooserShortcutPanelFilesメソッドが追加されました。とはいうものの、このクラスを直接使うことはまずないはずです。

 

ということで、標準で提供されているモジュールをすべて合わせても、変更は少ないですね。