2019/06/15

JJUG CCC 2019 Spring

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

もう1月も前ですが、5月18日にJJUG CCC 2019 Springが開催されました。Oracle Codeの次の日ですね。

今回はProject LoomのFiberについてプレゼンしました。資料はこちら。

Fiberはいわゆる軽量スレッドです。

プレゼンではなぜ軽量スレッドが必要なのかという部分について、かなり時間をかけて説明しました。

OSに強く結びついたThreadを使った場合、コンテキストスイッチにコストがかかります。時間もかかるし、メモリも多く使用します。

これは、スレッドごとに用意されるJVM Stackをすべて退避させたり、もどしたりする必要があるからです。JVM Stackは、そのスレッドでコールされているメソッドコールのスタックで、メソッドで使われるローカル変数やオペランドスタックも含みます。

また、OSがスレッドのスケジューリングを管理しているため、コンテキストスイッチがいつ行われているかもJava側からは管理することができません。

そのために求められているのが、JVMで管理する軽量スレッド、つまりFiberというわけです。

これに対し、Fiberを使えば、ワーカースレッド上で動作するため、スケジューリングはJVMで管理でき、状態の退避用のメモリも少なくてすみます。

Fiberでは、スケジューリングには既存のFork/Join Frameworkが使用されます。コンテキストスイッチには処理の中断、再開のための仕組みが必要ですが、これはContinuationで行います。Continuationはいわゆる継続を実現させるための仕組みですが、JavaのContinuationは限定継続になります。

プレゼンの中ではContinuationをかなり強引にwait-notifyAllと同じようなものと説明してしまったため、誤解させてしまったのではないかと反省しています。もちろん、Continuationもwait-notifyAllも処理の中断・再開をするという機能はありますが、一般的なContinuationでできることは処理の中断・再開だけではありません。ちょっと説明不足でした。

しかし、今のところProject LoomではContinationを積極的に活用するようなシナリオはないように見えます。現状は、I/O待ちのためにパフォーマンスが落ちていることに対して、Fiberを使ってI/O待ちをなるべく解消することがメインの目的のようです。

これは、処理の中断・再開を行うFiberのparkメソッド、unparkメソッドがデフォルトのアクセス制御であることからも分かります。park/unparkを使用しているのは、ReentrantLockクラスなどのロック系のクラスや、java.nio.channelパッケージのソケット通信などのクラスなどです。

プレゼンの中では前日に行ったOracle Codeのデモについても説明しました。JDKに含まれているHTTPサーバーであるcom.sun.net.httpserver.HttpSErverクラスを使用して、Fiberを指定しているだけです。これだけで、既存のExecutorServiceインタフェースを使用した場合よりもスレッド数やスループットが向上しました。

とはいうものの、現状Fiberはそこまで速くありません。

コンテキストスイッチが起きないようにうまくタスク分けして、I/O待ちも多重化するなどして待たなければいけないスレッドを最小化するなどのチューニングを行えば、Fiberより高いパフォーマンスを得ることができます。

ただ、それをするには設計やチューニングなどの高度な知識や経験が求められます。Fiberを使うことで誰でも簡単にパフォーマンスを向上させられるということが、Fiberの意義の1つなのではないかと感じています。

また、Fiberのさらなるパフォーマンス向上のためにJVM Stackを操作することも考えられているようなので、今後に期待したいですね。

 

さて、以下は会場やsli.doでの質問とその回答です。

Q Fiberの中断前と再開後でワークスレッドが変わることがあるのか?

A. あります。

キターーーーーーー 「この分野は素人なのですが」な質問!!!

なんとか答えられてよかったですw

さて、現状はFiberはシリアライザブルではないのですが、シリアライザブルにする計画があります。このため、ワークスレッドが変化することもありますし、処理途中のFiberを他のCPUに移動させてそこで再開というシナリオも考えられます。

質問した伊藤さんも心配されていますが、ロガーのようにスタックトレースを保持させるようなものは、Fiberにするとやばいかもしれません。

Q. ある時点の処理で中断していたものを複数回再実行することはできるか?

A. 一般的な継続だとこれができるのですが、今のところJavaのFiberでは計画されていないようです。

Fiberは継続を行うための状態管理に既存のJVM Stackをそのまま使っています。そのため、再実行を行うには、JVM Stackを操作するバイトコードが必要になるはずですが、そこまでやるとかなり大がかりになってしまうためかもしれません。

Q. ワークスレッドは自動的に用意されるのか?

A. されます。

デフォルトではForkJoinPoolを利用してワークスレッドの割り当てを行っています。もちろん、他のスレッドプールに置き換えることもできます。

Q. OSによらないThreadがFiberだとしたら、Green Threadのようなもの?

A. まさにGreen Threadです。

Javaの初期のころ、Solaris向けのJavaはネイティブのスレッドではなく、JVMが管理するスレッドを使用していたのですが、それがGreen Threadです。

Q. Kotlinのcoroutineと何が違うのか?

A. Kotlinのことをよく知らないのですが、同じような機能のようですね。

Q. Continuationのscopeがよく分からない。コンテキストスイッチを行いたい複数のFiberからなるグループのようなもの?

A. 継続処理を行いたい範囲をしめすものです。

どこからでも自由に中断・再開を行うのは難しいので、継続ができる範囲を決めているという感じです。Fiberも内部でスコープを持っていて、Fiberのタスク処理の中だけで継続を行っています。

Q. ThreadとFiberの使い分け指針が知りたい

A. I/Oの待機やロックの取得を含む非同期処理であればFiberを使うのがよいと思います。

計算処理だけであるならパラレルストリームやFork/Join Frameworkを使いましょう。それ以外だったらThreadになると思いますが、今でもExecutorServiceにタスクを渡すのが主で、Threadを直接使うことはまずないはずです。

Q. 既存のThreadをFiberに置き換えるイメージがつかめません。ExecutorServiceでThreadを使うようなことはFiberでもできるのでしょうか。

A. Fiber.scheduleメソッドがExecutorService.submitメソッドのような感じです。

Fiber.scheduleメソッドはstaticメソッドなので、ExecutorServiceのようにExecutorsクラスでExecutorServiceインスタンスを生成して、それからsubmitするまでを行っているような感じです。

Q. Loom入りのJDKを含んだDocker Imageは公開されているか?

A. 私が調べたときにはなかったです。

Docker上でLoomのJDKをビルドしようとするとなぜか落ちてしまうので、私はローカルな環境でビルドしてからそれをコピーするDockerfileを作ってDockerのイメージを作ってました。

Q. Loomのリリース予定は未定だとしても、現時点での目標とかはあるのでしょうか?

A. 明確なリリース予定時期がFiberを作っている人たちの間ではあるのかもしれませんが、私には分からないです。

少なくとも、Windowsで動作できるようにならないとリリースはできないので、そこが最低限の目標となるのではないでしょうか。

2019/05/22

Oracle Code Tokyo 2019

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

5月17日にシェラトン都ホテルで開催されたOracle Code Tokyoのキーノートで、@bitter_foxさんと一緒にデモをしてきました。

キーノートは2部構成で前半がOracleのGeorges Saabさん、後半がウルシステムズの漆原さんがモデレータのパネル。
私たちは前半のGeorgesのキーノートの中でデモを行いました。

 

当初、Oracle側からはJava SE 12か13の新機能についてデモをしてくれということだったのですが... switch文がswitch式になって書き方が変わったよとデモしてもつまらないですよね。

そこで、12もしくは13ではなく、今後登場する機能のデモでもいいかと聞いてみたら、OKが出たのです。

そこで、@bitter_foxさんと検討して、Project PanamaのVector APIとProject LoomのFiberのデモを行うことにしました。

 

デモのシナリオとしては以下の通り。デモの題材は画像処理のソフトフォーカスエフェクトです。

  1. デスクトップアプリで、Vector APIと従来のループを使った画像処理を比較
  2. デスクトップアプリはJavaFXで記述しているが、REST APIとしても画像処理できるようにする
  3. 画像処理を行うインスタンスを複数起動して、それらをコールするフロントのWebサーバーをFiberを使用して実装する
  4. 従来のThreadを使ったサーバーと、Fiberを使ったサーバーに対し負荷テストを行い、スループットなどの違いをリアルタイムで見せる

見てすぐわかるというところに、かなりこだわってみました。

上の写真がデスクトップアプリのスクリーンショットです。上部に24枚の写真のサムネイルが並んでおり、順々に画像処理していきます。だいたい、3倍から4倍ぐらいパフォーマンスが向上しました。

負荷テストの結果はGrafanaを使って表示させています。ちょっと条件を作りこんでいる部分はあるのですが、2倍程度のスループット向上が実現できました。

まぁ、お分かりだとは思いますけど、Vector APIの部分を櫻庭が作り、Fiberの部分を@bitter_foxさんが作っています。サーバーサイドがまったく分からない櫻庭の実力の低さが露呈してしまいましたw Grafanaを使ったモニタリングの仕組みなども、全部@bitter_foxさんにおまかせです 😅

それでも、見ていただいた方には結構好評だったようで、ほんとよかったです!

デモのコードについては、近日公開予定です。公開したら、解説のエントリーを書きます。

 

ところで、このデモで使った写真ですが、今までのJavaOneなどで撮った写真を使ってます。すべての人物が分かるのであれば、それはJavaの歴史にかなり精通しているといえると思いますよ。歴史的瞬間もあったりします。

ところで、土下座している人は何を謝っているんですかねぇw

 

2019/04/04

Java SE 12: Not Mentioned in JEP

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

Java SE 12 was released in 19th March!

So, I'll show updates in Java SE 12. But, I don't explain about the new features mentioned in JEPs.

In general, the updates in Java SE 12 are fewer than other Java SE releases. In the point of syntax, switch statement changes to switch expression (JEP 325). It's big change! But, the API updates are not big.

 

Removed APIs

There are 5 removed methods in Java SE 12.

All are the methods that forRemoval of @Deprecated set true in Java SE 10.

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

Because these methods are only finalize methods, so you feel no difficulty for coding. If you have difficulty, the design may be wrong.

 

In addition, 4 methods were removed.

All of them are override getCause methods, and these classes use getCause method of super class after removing.

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

 

APIs proposed for Removal

There are no APIs that are added for removal in Java SE 12.

 

Added APIs

java.lang.constant package

A package was added to java.base module!

But, most of developer seem never to use the package. Because, the pacakge was introduced for "Condy." Cody is for dynamic constant loading as an equivalent to Indy for dynamic method invocation.

APIs included in new package are based on JEP 334: JVM Constants API.

In addition, some interfaces/class and methods are introduced in java.lang.invoke package.

Added interfaces/class are shown below:

  • TypeDescriptor interface
  • TypeDescriptor.OfField interface
  • TypeDescriptor.OfMethod interface
  • VarHandle.VarHandleDesc class

There are 3 methods added:

  • Optional<MethodHandleDesc> MethodHandle.describeConstable()
  • String MethodType.describeString()
  • Optional<MethodTypeDesc> describeConstable()

Moreover, there are 4 methods added in java.lang.Class class:

  • Class<?> arrayType()
  • Class<?> componentType()
  • Optional<ClassDesc> describeConstable()
  • String descriptorString()

 

Besides these, Enum class implements Constable interface, and Enum.EnumDesc inner class is added. Integer/Long/Float/Double/String classes are updated to implement ConstantDesc insterface. These classes are added describeConstant method and resolveConstantDesc method.

However, most of developers seems not to use these new condy APIs for usual software development.

 

java.io package

InputStream class

InputStream class has skip method, and a related method is added:

  • void skipNBytes(long n)

skip method javadoc said "The skip method may, for a variety of reasons, end up skipping over some smaller number of bytes." For this reason, skip methods return actual number of skipping over.

Comparatively, skipNBytes method skip over and discards exactly n bytes. Because of exactly skipping over, type of skipNBytes return value is void.

 

java.lang package

I listed condy related updates of java.lang package, so I'll show other updates.

 

Character.UnicodeBloch/UnicodeScript class

Java SE 12 starts to support Unicode 11.0, but this update is not described in JEP. Java SE 11 supports Unicode 10.0.

For this update, some Unicode blocks and scripts are added. For example, CHESS_SYMBOLS that means chess pawns is added.

 

String class

As previously described, String class added two condy related methods. In addition, a method is added:

  • String indent(int n)

indent method inserts specified number of white spaces into head of line, and inserts new line (\n) into end of string.

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>

If argument is minus value, indent method removes specified number of white spaces. In this case, indent method only remove white spaces.

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

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

jshell>

Originally, Java SE 12 planed to introduce JEP 326 Raw String Literals, but gave it up.

JEP 326 includes some String class methods definition. However, these methods were also abandoned.

Only indent method was added to String class. I don't know why.

 

java.net package

SecureCacheResponse class

A method to get SSL/TLS session is added to SevureCacheResponse class.

  • Optional<SSLSession> getSSLSession()

 

ServerSocket class

A constructe is added to ServerSocket class!

  • protected ServerSocket(SocketImpl impl)

However, this constructer is proteced method, so we don't use it in normal software development.

 

java.nio.file package

Files class

A method for finding mismatch points of two fiels is introduced:

  • static long mismatch(Path path, Path path2)

 

mismatch method finds the first difference between path and path2, and returns the position of the first difference of two.

If two files are same, mismatch method returns -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
|  Welcome to JShell -- Version 12
|  For an introduction type: /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 package

CompactNumberFormat class

We occasionally want to show numbers using K or M like 10K (10,000). But, Java didn't have such number formatter API before.

Then, Java SE 12 introduced CompactNumberFormat class.

CompaceNumberFormat is a subclass of NumberFormat class, so usage is same. There is a constructor, but it is convenient for us to use factory methods of NumberFormat class.

jshell> import java.text.*

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

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

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

jshell> formatter.format(15678)
$5 ==> "16K"

jshell>

The method to get CompactNumberFormat object is NumberFormat.getCompactNumberInstance method. If you use it without argument, you get CompactNumberFormat object of default locale.

In above example, the default locale is English, so the result of formatting 10,000 was 10K.

It's to be noted that default rounding is RoundingMode.HALF_UP. So, CompactNumberInstance method returns 16K when formatting 15,678.

If you'd like to use RoundingMode.FLOOR, you should use setRoundingMode method.

When formatting the number more than 1,000,000, CompactNumberFormat object uses M.

jshell> formatter.format(12_345_678)
$3 ==> "12M"

jshell>

It's also to be noted that the return type of getCompactNumberInstance method is not CompactNumberFormat class but NumberFormat class.

We, Japanese count number by 104not 103. Number unit of 104 is "万" and 108 is "億".

If you'd like to use "万" or "億", you chage locale.

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

jshell> formatter.format(100_000)
$5 ==> "10万"

jshell> formatter.format(123_456)
$6 ==> "12万"

jshell>

Second argument type of getCompactNumberInstance method is Number.Style enum. It is introduced in Java SE 12.

There are two constants defined in Number.Style: LONG and SHORT. No argument getCompactNumberInstance method uses SHORT by default.

When locale is jp (Japanese), 100,000 is formatted to "10万" and 123,456 is formatted to "12万".

 

In case of LONG;

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

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

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

jshell>

"10 thousand" is compact, isn't it 🤔.

 

NumberFormat class

As previously explained, two static factory methods are introduced:

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

java.text.spi.NumberFormatProvider were also added getCompactNumberInstance method.

 

NumberFormat.Field class

Two constants are added:

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

 

NumberFormat.Style enum

Style enum is also explained previously. It is for constructing CompactNumberFormat object.

There are two constants: LONG and SHORT.

 

java.util.concurrent package

CompletionStage class

5 similar methods to exceptionally method are introduced.

  • 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 method is called when an exception is thrown in the previsou stage. If no exception in the previous stage, exceptionally method are skipped, and then next stage are executed.

exceptionallyAsync methods are called asynchronously as can be seen by method name.

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>

Executing by JShell, you can see exceptionally method is executed in main thread, but exceptionallyAsync method is executed in worker thread managed by ForkJoinPool.

 

The Argument of exceptionally method is a lambda expression that gets Throwable object as argument and return something value. On the other hand, the return value tyep of exceptionallyCompose method is CompletionStage class.

thenCompose method returns also CompletionStage object, so exceptionallyCompose is similar to thenCompose. As you may know, exceptionallyComposeAsync method is asynchronous version of exceptionallyCompose.

 

java.util.stream package

Collectors class

A method that execute two terminal operations and gathers these return values is introduced:

  • 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)

First and second argument is objects implicated Collect interface (usually we use static methods of Collectors class.) Third argument is a lambda expression for combining first and second Collect objects return values.

The following shows string concatination and couting number of characters, and then combining these two operations return values.

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>

It seems very useful!

 

javax.lang.model package

SouceVersion class

Every version up, SouceVersion add a constant:

  • static final SourceVersion RELEASE_12

On the other hand, SourceVersion class is now implementing Constable interface.

 

Others

javax.naming.ldap.spi package is introduced for LDAP service provider. The package includes two class: LdapDnsProvider and LdapDnsProviderResult.

javax.net.ssl.HttpsURLConnection class is added getSSLSession method.

 

Swing is also added a method.

getChooserShortcutPanelFiles method is introduced in javax.swing.filechooser.FileSystemView class. But, usually we don't use the method. It is for internal use.

 

That's all.

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メソッドが追加されました。とはいうものの、このクラスを直接使うことはまずないはずです。

 

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

 

2018/10/13

Java SE 11 not mentioned in JEP

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

Java SE 11 was release on 25th Sep.

Features of Java SE are proposed in JEP (JDK enhancement proposal), and Java SE 11 includes 17 JEPs.

However, there are many small updates not mentioned in JEP. So, I summarized these small updates, especially java.base module.

I excepted security features, because I don't have enough knowledge about security.

 

Removed API

Java is the language that give weight to compatibility, however Java compatibility policy has been changed since Java SE 9.

In fact, there are many APIs removed in Java SE 11.

Module

The biggest incompatibility of Java SE 11 is to remove modules about Java EE. This was proposed in JEP 320.

These modules were deprecated in Java SE 9, but we can still use them in Java SE 9 and 10.

However, the modules were removed finally in Java SE 11!

The removed modules are following:

  • java.se.ee
  • java.activation (JAF)
  • java.corba
  • java.transaction (JTA)
  • java.xml.bind (JAXB)
  • java.xml.ws (JAX-WS)
  • java.xml.ws.annotation (JAX-WS)

You can use JAF, JTA, JAXB and JAX-WS of Java EE (Jakarta EE). These modules are also registered in Maven central reposity. I wrote the article how to use JAXB and JAF in Maven project.

No alternative about CORBA, but I think no one uses CORBA any more.

In addition, JavaFX modules in Oracle JDK were removed (OpenJDK doesn't include JavaFX modules.)

  • javafx.base
  • javafx.graphics
  • javafx.controls
  • javafx.fxml
  • javafx.media
  • javafx.web
  • javafx.swing

JavaFX is developed by OpenJFX project in OpenJDK. JavaFX 11 is also released before Java SE 11.

New JavaFX Site was opened at the same time as JavaFX 11 release. You can download JavaFX 11 and refere Javadoc of JavaFX 11 at the site.

Class

There were many changes about security API. Following class was removed:

  • java.security.Policy

Method

forRemovel atribute of @Deprecated annotation was introduced in Java SE 9. Type of forRemoval is boolean, and true means that target API will be removed.

In Java SE 11, following methods that forRemoval value were true were removed:

  • java.lang.Runtime.runFinalizersOnExit(boolean)
  • java.lang.SecurityManager.checkAwtEventQueueAccess()
  • java.lang.SecurityManager.checkMemberAccess(java.lang.Class, int)
  • java.lang.SecurityManager.checkSystemClipboardAccess()
  • java.lang.SecurityManager.checkTopLevelWindow(java.lang.Object)
  • java.lang.System.runFinalizersOnExit(boolean)
  • java.lang.Thread.destroy()
  • java.lang.Thread.stop(java.lang.Throwable)

All of removed methods were that forRemoval value were true in Java SE 9.

APIs proposed for Removal

Follwoing APIs were proposed for removal in Java SE 11.

Class

  • java.util.jar.Pack200

Interface

  • java.util.jar.Pack200.Packer
  • java.util.jar.Pack200.Unpacker

Pack200 class was proposed by JEP 336.

All of deprecated API are listed in Deprecated in Javadoc.

 

Added APIs

java.io package

ByteArrayOutputStream class

  • void writeBytes(byte[] b)

This method is equivalent to write(b, 0, b.lenght).

FileReader class

Two constructors are introduced.

  • FileReader(java.io.File file, java.nio.charset.Charset charset)
  • FileReader(java.lang.String fileName, java.nio.charset.Charset charset)

Finally, we can specify charset to create FileReader object!

FileWriter class

Four constructers with charset are also introduced as FileReader.

  • FileWriter(java.io.File file, java.nio.charset.Charset charset)
  • FileWriter(java.io.File file, java.nio.charset.Charset charset, boolean append)
  • FileWriter(java.lang.String fileName, java.nio.charset.Charset charset)
  • FileWriter(java.lang.String fileName, java.nio.charset.Charset charset, boolean append)

InputStream class

  • static InputStream nullInputStream()
  • byte[] readNBytes(int len)

nullInputStream method is a factory method to create InputStream object that reads no bytes.

readNBytes method was introduced in Java SE 9. In Java SE 11, overload method is also introduced.

You need to specify byte array for readNBytes in Java SE 9, but no need byte array argument in Java SE 11. You only specigy lenght of read bytes.

However, I think the implementation is not so efficient at the present. Therefore, if reading data repeatedly in a loop, you should use a buffer byte array instead of readNBytes in Java SE 11. If reading all data at once, readAllbytes method is suitable.

OutputStream class

  • static OutputStream nullOutputStream()

nullOutputStream method is a factory method to create OutputStream object that writes no bytes as InputStream.

Reader class

  • static Reader nullReader()

Writer class

  • static Writer nullWriter()

java.lang package

CharSequence interface

  • static int compare(java.lang.CharSequence cs1, java.lang.CharSequence cs2)

compare method is a static method to compare two arguments.

I think it is a simple delegation method, but the implementation of compare methods compares two arguments character one by one.

Character class

  • static String toString(int codePoint)

toString method to create String object from char argument is existed, but no int argument until Java SE 11. It means we can use a codepoint of Unicode to create String object.

Character.UnicodeBlock class

Java SE 11 enable to use Unicode 10. With this update, 18 constans of Unicode block are added.

Character.UnicodeScript enum

With introducing Unicode 10, 10 script name of Unicode are added.

Class class

  • Class<?> getNestHost()

getNestHost method returns host class object of nested class. When class is non-nest, array, or primitive, it returns self class object.

  • Class<?>[] getNestMembers()

getNestMembers returns nested members.

  • boolean isNestmateOf(Class<?> c)

Comparing to the host class of argument Class object. When argument is array or primitive type, it returns false.

These three methods were porposed by JEP 181 Nest-Based Access Control.

String class

  • boolean isBlank()

isBlank return true when string is empty or only white space.

  • Stream<String> lines()

lines method divides string by line break, and makes Stream object.

  • String repeat(int count)

repeat method makes a concatenated string repeatedly by count argument value. For example, "a".repeat(3) returs "aaa".

  • String strip()
  • String stripLeading()
  • String stripTailing()

These three methods remove all white spaces of leading and trailing.

strip method removes leading and trailing white spaces, stripLeading method removes only leading, and stripTailing method removes only tailiing.

strip and stripLeading methods are similar to trim method. But, trim method removes only Latin-1 white space.

To remove non-Latin-1 white space such as U+3000, you should use strip/stripLeading/stripTailing methods.

StringBuffer class

StringBuilder class

These two classes now implement Comparable interface. Therefore, we can use compareTo method of these classes.

java.lang.invoke package

A class was added to java.lang.invoke package.

  • ConstantBootstraps class

boostrap of this class name means bootstrap method for InvokeDynamic bytecode. InvokeDynamic define how to process dynamically, and bootstrap is used for this purpose. InvokeDynamic is used for dynamic type JVM language such as JRuby and Lambda expression.

Java SE 11 introduced new bytecode, that is CONSTANT_Dynamic. CONSTANT_Dynamic is used for constant initialization, on the other hand InvokeDynamic is for method invoking.

CONSTANT_Dynamic also uses bootstrap method for defining constant initialization.

ConstantBootstraps is a utility class for bootstrap of CONSTANT_Dynamic.

By the way, javac of Java SE 11 doesn't use CONSTANT_Dynamic. In the future, javac will make class file including CONSTANT_Dynamic.

java.lang.ref package

Reference class

  • Object clone()

The reason clone method is overridden is to indicate Reference class can't clone. Therefore, the clone method always throws CloneNotSupportedException.

But, I think root cause is that clone method is defined in Object class. Although the root cause is clear, the cause can't be fixed anymore. So, clone method in Reference class is work-around.

java.nio package

ByteBuffer class

  • int mismatch(ByteBuffer that)

CharBuffer class

  • int mismatch(CharBuffer that)

DoubleBuffer class

  • int mismatch(DoubleBuffer that)

FloatBuffer class

  • int mismatch(FloatBuffer that)

IntBuffer class

  • int mismatch(IntBuffer that)

LongBuffer class

  • int mismatch(LongBuffer that)

ShortBuffer class

  • int mismatch(ShortBuffer that)

mismatch method compares this buffer and argument buffer, and returns the relative index that two buffers are diifferent. If the buffers are equal, it returns -1.

The relative means that mismatch method scans from the position to the limit of buffers.

Even if the first absolute mismatch index is before the position, mismatch method doesn't check it.

java.nio.chanels package

SelectionKey class

  • int interestOpsAnd(int ops)
  • int interestOpsOr(int ops)

SelectionKey class is used for non-blocking socket communication, and defines some constans of operations.

These constants are expressed a bit, and can do bit operations such as AND or OR. For example, OP_READ is 0b0001, and OP_WRITE is 0b0100.

We can get/set the present operation value by interestOps method.

Above two methods are combination methods of interestOps and AND or OR operation.

key.interestOpsAnd(ops) is equivalent to key.interestOps(key.interestOps() & ops). At the same way, key.interestOpsOr(ops) is same as key.interestOps(key.interestOps() | ops).

Selector class

Selector class is also used for non-blocking communitation. Now, we can describe the non-blocking process by lambda expression.

  • int select(java.util.function.Consumer<SelectionKey> action)
  • int select(java.util.function.Consumer<SelectionKey> action, long timeout)
  • int selectNow(java.util.function.Consumer<SelectionKey> action)

Until Java SE 11, we get SelectionKey object by selectedKeys method, and handle it according to the operation of SelectionKey object.

Now, we can describe like following:

    selector.select(key -> {
        try {
            if (key.isAcceptable()) {
                // OP_ACCEPT case
                ServerSocketChannel serverSocket = (ServerSocketChannel) key.channel();
                // change to Non-Blocking mode
                serverSocket.configureBlocking(false);
                // register to Selector
                serverSocket.register(selector, SelectionKey.OP_READ);
            } else if (key.isReadable()) {
                // OP_READ case
                SocketChannel socket = (SocketChannel) key.channel();
                // read contents...
            }
        } catch (IOException ex) {
            // exception handling
        }
    });

I don't like exception handling in lambda expression, but description is much easier than boefore.

java.nio.file package

Path interface

static Path of(java.lang.String first, java.lang.String... more)

static Path of(java.net.URI uri)

Two factory methods were added to Path interface.

The former factory method is equivalent to FileSystems.getDefault().getPath(first, more). It means the former factory method deal with phisical file system.

The latter factory method returns Path object of default phisical file system if schema of URI is "file". If not, it checks whether schema of URI is possible to deal with or not. If possible, the factory method calls getPath(uri) method of FileSystemProvider class.

Files class

  • static String readString(java.nio.file.Path path)
  • static String readString(java.nio.file.Path path, java.nio.charset.Charset cs)
  • static Path writeString(java.nio.file.Path path, java.lang.CharSequence csq, java.nio.file.OpenOption... options)
  • static Path writeString(java.nio.file.Path path, java.lang.CharSequence csq, java.nio.charset.Charset cs, java.nio.file.OpenOption... options)

Files class defines readAllLines method to read the contents at one time, but type of return value is List<String>. On the other hand, readString methods returns a String object of file contes including line break code.

In the same way, writeString writes a CharSequence object to path at one time.

java.util package

Collection interface

  • <T> T[] toArray(IntFunction<T[]> generator)

Collection intterface added an overload of toArray method. Argument of new toArray method is a generator function to allocate the returned array.

Optional class

OptionalDouble class

OptionalInt class

OptionalLong class

  • boolean isEmpty()

isEmpty method is reverse of isPresent method.

java.tuil.concurrent package

TimeUnit enum

long convert(java.time.Duration duration)

convert method converts duration to time unit. For example, TimeUnit.MILLISECONDS.convert(Duration.ofMinutes(1L)) returns 60000.

java.util.function package

Predicate interface

  • <T> Predicate<T> not(Predicate<? super T> target)

not method creates Predicate object that indicates negative condition of target.

java.util.regex package

Pattern class

  • Predicate<String> asMatchPredicate()

asPredicate method was introduced in Java SE 8. The asPredicate method creates Predicate object to use for an argument of fiter method, mid-operation of Stream pipeline.

However, Predicate object created by asPredicate is equivalent to s -> s.matcher().find(). It means partial check.

asMatchPredicate method also creates Predicate object, but the object checks whole string. It is equivalent to s -> s.matcher().matches().

    Pattern pattern = Pattern.compile("bc");
    List<String> list = List.of("abc", "bc", "bcd");

    list.stream()
        .filter(pattern.asPredicate())
        .forEach(System.out::println);

    list.stream()
        .filter(pattern.asMatchPredicate())
        .forEach(System.out::println);

The former result of above code is "abc", "bc", and "bcd", but the latter result is only "bc".

java.util.zip package

Deflater class

  • int deflate(java.nio.ByteBuffer output)
  • int deflate(java.nio.ByteBuffer output, int flush)
  • void setDictionary(java.nio.ByteBuffer dictionary)
  • void setInput(java.nio.ByteBuffer input)

Deflater class is used for ZLIB compress.

New four methods are all overloads. Argument of exsited methods are byte array, however one of new methods are ByteBuffer.

Inflater class

  • int inflate(java.nio.ByteBuffer output)
  • void setDictionary(java.nio.ByteBuffer dictionary)
  • void setInput(java.nio.
  • ByteBuffer input)

Inflater class is used for uncompressing ZLIB compressed contents.

As same as Deflater class, new methods enable to use ByteBuffer for argument.

2018/09/26

JEPでは語れないJava SE 11 - java.baseモジュール編

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

今日の早朝、Java SE 11がリリースされました。

Java SEの機能はJEPでまとめられていますが、JEPに入っていないこまごまとした変更もいろいろ入ってます。ということで、Java SE 10に引き続き、Java SE 11についてもまとめてみます。

量が多いので、今回はjava.baseモジュールだけです。他のモジュールも引き続きまとめるつもりです。

基本的にはABC順にならんでいます。重要度順にしようかと思ったのですが、量が量なので...

また、セキュリティ系のAPIはちゃんと理解していないので、省略してます。

 

廃止になったAPI

Javaは互換性を重視する言語ですが、Java 9から使われなくなった機能はほんとに削除対象になっています。

Java SE 11でも多くのAPIが廃止になっています。

モジュール

Java 11で一番大きな非互換性となるのが、Java EE関連のモジュールの削除です。これはJEP 320で提案されていました。

これらのモジュールはJava 9/10でも標準モジュールパスからは外されていましたが、Java 11でほんとうに削除されてしまいました。

  • java.se.ee
  • java.activation (JAF)
  • java.corba
  • java.transaction
  • java.xml.bind (JAXB)
  • java.xml.ws (JAX-WS)
  • java.xml.ws.annotation

JAFやJAXB、JAX-WSはJava EEのものを使うようにすればOKです。Mavenのセントラルレポジトリにも登録されているので、MavenやGradleを使われている場合はそちらを使うのが便利です。

CORBAは代替のAPIはないのですが、今さら使う人はいないでしょう。

また、Oracle JDKに含まれていたJavaFXはOracle JDKからも削除されてしまいました。

  • javafx.base
  • javafx.graphics
  • javafx.controls
  • javafx.fxml
  • javafx.media
  • javafx.web
  • javafx.swing

JavaFXはOpenJDKのOpenJFXプロジェクトで開発が進められています。Java SE 11に先立って、JavaFX 11もリリースされています。

JavaFXはバイナリの配布やJavadocも含めて、新しいJavaFXのサイトで公開されています。

クラス

セキュリティ系のAPIはいろいろと変化があります。

クラスも廃止されています。

  • java.security.Policy

メソッド

@DeprecatedアノテーションでforRemovalがtrueのメソッドで、以下のメソッドが廃止されました。

  • java.lang.Runtime.runFinalizersOnExit(boolean)
  • java.lang.SecurityManager.checkAwtEventQueueAccess()
  • java.lang.SecurityManager.checkMemberAccess(java.lang.Class, int)
  • java.lang.SecurityManager.checkSystemClipboardAccess()
  • java.lang.SecurityManager.checkTopLevelWindow(java.lang.Object)
  • java.lang.System.runFinalizersOnExit(boolean)
  • java.lang.Thread.destroy()
  • java.lang.Thread.stop(java.lang.Throwable)

いずれもJava SE 9でforRemovalがtrueになったメソッドです。

廃止予定のAPI

Java SE 11では以下のAPIが廃止予定に追加されました。

クラス

  • java.util.jar.Pack200

インタフェース

  • java.util.jar.Pack200.Packer
  • java.util.jar.Pack200.Unpacker

Pack200クラスはJEP 336で提案されていました。

追加されたAPIも含めたDeprecatedなAPIはJavadocのDeprecatedで参照できます。

 

追加されたAPI

java.ioパッケージ

ByteArrayOutputStreamクラス

  • void writeBytes(byte[] b)

このメソッドはwrite(b, 0, b.lenght)と同じ処理になります。

FileReaderクラス

コンストラクタが2つ追加されました。

  • FileReader(java.io.File file, java.nio.charset.Charset charset)
  • FileReader(java.lang.String fileName, java.nio.charset.Charset charset)

やっと文字コードを指定できるようになりました。

FileWriterクラス

FileWriterクラスもFileReaderクラスと同様に、コンストラクタで文字コードを指定できるようになりました。

  • FileWriter(java.io.File file, java.nio.charset.Charset charset)
  • FileWriter(java.io.File file, java.nio.charset.Charset charset, boolean append)
  • FileWriter(java.lang.String fileName, java.nio.charset.Charset charset)
  • FileWriter(java.lang.String fileName, java.nio.charset.Charset charset, boolean append)

InputStreamクラス

  • static InputStream nullInputStream()
  • byte[] readNBytes(int len)

nullInputStreamメソッドは何も返さないInputStreamオブジェクトを作るためのファクトリメソッドです。

readNBytesメソッドはJava 9で追加されましたが、そのオーバーロードがさらに追加されました。

Java 9で追加されたreadNBytesメソッドは引数にバイト配列を指定する必要がありましたが、Java 11でのオーバーロードでは読み込み長だけ指定すれば、配列を返してくれます。

とはいうものの、ループなどで何度も読む場合はバッファを使いまわした方が効率がいいですし、一度ですべて読み込むのであればreadAllBytesメソッドを使うので、使いどころが難しいかもしれません。

OutputStreamクラス

  • static OutputStream nullOutputStream()

InputStreamクラスと同様に、OutputStreamクラスにも何もしないストリームを作るファクトリメソッドが追加されました。

Readerクラス

  • static Reader nullReader()

Writerクラス

  • static Writer nullWriter()

java.langパッケージ

CharSequenceインタフェース

  • static int compare(java.lang.CharSequence cs1, java.lang.CharSequence cs2)

staticメソッドで比較できるようになりました。

てっきり、シンタックスシュガー的なメソッドかと思ったら、1文字ずつ比較してましたよw

Characterクラス

  • static String toString(int codePoint)

char型変数からStringオブジェクトを生成するメソッドはありましたが、補助文字を使えるcodePointからもStringオブジェクトを作れるようになりました。

Character.UnicodeBlockクラス

Unicode 10に合わせて、ブロックを表す定数が18個追加されました。いっぱいあるので省略。

Character.UnicodeScript列挙型

こちらもUnicode 10に合わせた文字スクリプト名が10個追加されました。

Classクラス

  • Class<?> getNestHost()

入れ子になったクラスのホストクラスを返します。入れ子になっていないクラス、配列、プリミティブおよびvoidは自分自身を返します。

  • Class<?>[] getNestMembers()

入れ子になったメンバーを返します。

  • boolean isNestmateOf(Class<?> c)

引数で指定したクラスとホストクラスが同じであるかどうかを調べます。配列やプリミティブ型の場合はfalseが返るようです。

これらの3つのメソッドはJEP 181 Nest-Based Access Controlで提案されたものです。

Stringクラス

  • boolean isBlank()

文字列が空かホワイトスペースだけの場合、trueが返ります。

  • Stream<String> lines()

文字列を改行コードで区切って、ストリーム化します。

  • String repeat(int count)

引数で指定された回数だけ繰り返した文字列を作ります。たとえば、"a".repeat(3);だと"aaa"が返ります。

  • String strip()
  • String stripLeading()
  • String stripTailing()

文字列の先頭と末尾のホワイトスペースを取り除くためのメソッド。

striptメソッドが先頭と末尾、stripLeadingメソッドが先頭だけ、stripTailingメソッドは末尾だけ処理します。

先頭のホワイトスペースを取り除くtrimメソッドと動作が似ているのですが、trimメソッドはLatin-1のホワイトスペース(半角のスペースとタブ、改行)だけを取り除いているようです。

このため、全角のスペースなどを取り除くにはstripメソッドを使用する必要があります。

StringBufferクラス

StringBuilderクラス

この2つのクラスはComparableインタフェースを実装するようになりました。

このため、compareToメソッドが使えるようになっています。

java.lang.invokeパッケージ

java.lang.invokeパッケージではクラスが追加されました!!

Java 10ではクラスの追加はなかったので、久しぶりのクラスの追加です。

  • ConstantBootstrapsクラス

bootstrapというのは動的に処理を決めるInvokeDynamic命令で使われていた機構です。JRubyや、ラムダ式でInvokeDynamic命令が使われています。

InvokeDynamic命令はメソッドコールに使われていたのですが、Java 11では定数に対してInvokeDynamic命令と同様に使用できるCONSTANT_Dynamic命令(condyと略されることが多いです)が追加されました(JEP 309)。

ConstantBootstapsクラスは、このcondyのbootstap用のユーティリティクラスです。

このため、普通の開発者がこのクラスを使うことはまずないと思います。

なお、Java 11のjavacでは、condyを使ったバイトコードは生成されないです。実際に使うようになるのはJava 12以降ですね。

java.lang.refパッケージ

Referenceクラス

  • Object clone()

なぜ、cloneメソッドが追加されたかというと、Referenceクラスはクローンできないということを明示するためです。このため、Referenceクラスのcloneメソッドをコールすると必ずCloneNotSupportedException例外が投げられます。

そもそも、Objectクラスにcloneメソッドが定義されていることが間違いのもとなんですよね。

java.nioパッケージ

ByteBufferクラス

  • int mismatch(ByteBuffer that)

CharBufferクラス

  • int mismatch(CharBuffer that)

DoubleBufferクラス

  • int mismatch(DoubleBuffer that)

FloatBufferクラス

  • int mismatch(FloatBuffer that)

IntBufferクラス

  • int mismatch(IntBuffer that)

LongBufferクラス

  • int mismatch(LongBuffer that)

ShortBufferクラス

  • int mismatch(ShortBuffer that)

mismatchメソッドはバッファ同士を比較して、違いのあった相対的な位置を返します。同一の場合は-1が返ります。相対といっているのは、バッファを調べるときにpositionの位置からlimitまでを調べるからです。

positionよりも前の位置の値が異なっていても、mismatchメソッドではチェックしません。

java.nio.chanelsパッケージ

SelectionKeyクラス

  • int interestOpsAnd(int ops)
  • int interestOpsOr(int ops)

ノンブロッキング通信で使用するSelectionKeyクラスは、操作対象を表す定数が定義されています。この定数はビットで表されていて、ANDやORをとることができます。たとえば、OP_READは0b0001、OP_WRITEは0b0100になっています。

これらの現在の値はinterestOpsメソッドで取得/設定できますが、それに対しAND処理とOR処理を加えたのが上記の2種類のメソッドです。

interestOpsAnd(ops)メソッドはkey.interestOps(key.interestOps() & ops)と同じ処理になります。同様にinterestOpsOr(ops)はkey.interestOps(key.interestOps() | ops)です。

Selectorクラス

SelectionKeyクラスと同様、ノンブロッキング通信で使用されるSelectorクラスでは、ノンブロッキングで行う処理の記述がラムダ式で記述できるようになりました。

  • int select(java.util.function.Consumer<SelectionKey> action)
  • int select(java.util.function.Consumer<SelectionKey> action, long timeout)
  • int selectNow(java.util.function.Consumer<SelectionKey> action)

今までは、selectedKeysメソッドでSelectionKeyオブジェクトを取り出して、処理していましたが、それが以下のように記述できます。

    selector.select(key -> {
        try {
            if (key.isAcceptable()) {
                // accept の場合の処理
                ServerSocketChannel serverSocket = (ServerSocketChannel) key.channel();
                // Non-Blocking モードに変更
                serverSocket.configureBlocking(false);
                // Selector への登録
                serverSocket.register(selector, SelectionKey.OP_READ);
            } else if (key.isReadable()) {
                // データが送られてきたときの処理
                SocketChannel socket = (SocketChannel) key.channel();
                // 読み込み処理
            }
        } catch (IOException ex) {
            // 例外処理
        }
    });

例外処理をラムダ式内でやらないといけないのが、ちょっとイヤですけど、記述はかなり簡単になりました。

java.nio.fileパッケージ

Pathインタフェース

static Path of(java.lang.String first, java.lang.String... more)

static Path of(java.net.URI uri)

Pathインタフェースにはファクトリメソッドが追加されました。

前者は FileSystems.getDefault().getPath(first, more)メソッドをコールする実装になっています。後者はスキーマがfileであればデフォルトのファイルシステム、それ以外であれば扱えるかどうかをチェックしてから、FileSystemProviderクラスのgetPath(uri)メソッドをコールしています。

Filesクラス

  • static String readString(java.nio.file.Path path)
  • static String readString(java.nio.file.Path path, java.nio.charset.Charset cs)
  • static Path writeString(java.nio.file.Path path, java.lang.CharSequence csq, java.nio.file.OpenOption... options)
  • static Path writeString(java.nio.file.Path path, java.lang.CharSequence csq, java.nio.charset.Charset cs, java.nio.file.OpenOption... options)

Filesクラスでは、ファイルを一括で読み込むreadAllLinesメソッドはあったのですが、この返り値の型はList<String>でした。これに対し、改行コードも含んだ1つの文字列として返すのがreadStringメソッドです。

同様にwriteStringメソッドではCharSequenceオブジェクトで表される文字列を一括で書き込みます。

java.utilパッケージ

Collectionインタフェース

  • <T> T[] toArray(IntFunction<T[]> generator)

配列を生成するための処理を記述できるようになりました。

Optionalクラス

OptionalDoubleクラス

OptionalIntクラス

OptionalLongクラス

  • boolean isEmpty()

isPresentメソッドの逆です。

java.tuil.concurrentパッケージ

TimeUnit列挙型

long convert(java.time.Duration duration)

durationで表される期間をTimeUnitで表される単位に変換します。TimuUnit.NANOSECONDSであればdurationをナノ秒に変換して返します。

java.util.functionパッケージ

Predicateインタフェース

  • <T> Predicate<T> not(Predicate<? super T> target)

targetで表される条件の否定を行うPredicateオブジェクトを返します。

java.util.regexパッケージ

Patternクラス

  • Predicate<String> asMatchPredicate()

Java 8でPatternクラスにasPredicateメソッドが追加されました。このasPredicateメソッドはストリームの中間操作のfilterメソッドの引数に使えるようなPredicateオブジェクトを生成していました。

ただ、このPredicateオブジェクトは s -> s.matcher().find() と同じ動作なので、文字列の部分一致を調べることになります。

そうではなく完全一致を行うようにするのがasMatchPredicateメソッドです。

    Pattern pattern = Pattern.compile("bc");
    List<String> list = List.of("abc", "bc", "bcd");

    list.stream()
        .filter(pattern.asPredicate())
        .forEach(System.out::println);

    list.stream()
        .filter(pattern.asMatchPredicate())
        .forEach(System.out::println);

これを実行すると、前者はabc, bc, bcdのすべてが表示されますが、後者はbcしか表示されません。

java.util.zipパッケージ

Deflaterクラス

  • int deflate(java.nio.ByteBuffer output)
  • int deflate(java.nio.ByteBuffer output, int flush)
  • void setDictionary(java.nio.ByteBuffer dictionary)
  • void setInput(java.nio.ByteBuffer input)

DeflaterクラスはZLIB圧縮を行うためのクラスです。

新たに追加された4種類のメソッドはいずれもオーバーロードです。既存のメソッドはすべてbyte[]が引数だったのに対し、新しいオーバーロードメソッドは引数にByteBufferクラスを使うことができます。

Inflaterクラス

  • int inflate(java.nio.ByteBuffer output)
  • void setDictionary(java.nio.ByteBuffer dictionary)
  • void setInput(java.nio.ByteBuffer input)

InflaterクラスはZIB圧縮を展開するためのクラスです。

Deflaterクラスと同様に、引数としてByteBufferクラスを使うことができるようになりました。

2018/08/17

Java 11 + JAXBのちょっとしたピットフォール

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

前回はJigsawでSerciveLoaderクラスを使用した時の挙動について紹介しました。

今回はその続きのようなもの。

Java SE 11からJAXBが外されてしまうのは、このブログでも何度も書いてます。

なので、JAXBを使うのであれば、GitHubのJAXBのページからダウンロードするか、MavenのCentral Repositoryを使うことになります。

JAXBは5つのJARファイルから構成されています。

APIはjaxb-api.jar、ランタイムがjaxb-impl.jarとjaxb-core.jarです。他の2つはXMLスキーマとJavaのクラスの変換を行うツールxjcとjxcのJARファイルです。

xjcなどを使わないのであれば、jaxb-api.jar、jaxb-impl.jar、jaxb-core.jarの3つだけを使用します。

また、JAXBはJavaBeans Activation Framework (JAF)も使用するので、こちらもダウンロードしておきます。ただ、GitHubのJAFのプロジェクトではJARファイルの配布はしていないので、Maven Central RepositryにあるJAFを利用します。

 

ここでは、簡単なサンプルとして、次に示すJAXBDemoクラスを作りました。

package net.javainthebox;

import java.io.File;
import javax.xml.bind.JAXB;
import javax.xml.bind.JAXBException;

import net.javainthebox.xml.Name;

public class JAXBDemo {
    public static void main(String... args) throws JAXBException {
        File file = new File("name.xml");
        Name sakuraba = JAXB.unmarshal(file, Name.class);

        System.out.println(sakuraba.getFirst() + " " + sakuraba.getLast());
    }
}

name.xmlファイルを読み込んでNameオブジェクトに変換するプログラムです。

Nameクラスはfirstとlastという2つの文字列のフィールドを持っているクラスです。

module-info.javaを次に示します。

module net.javainthebox.xml {
    requires java.xml.bind;
    opens net.javainthebox.xml;
}

requires文で指定しているjava.xml.bindモジュールがjaxb-api.jarファイルです。

opensでnet.javainthebox.xmlパッケージを指定しているのは、JAXBがリフレクションでNameクラスにアクセスするためです。

次にjava.xml.bindモジュールの依存性を調べてみましょう。これにはjarコマンドの--describe-module (もしくは省略形の-d) オプションで調べることができます。

C:\jaxb\mod>jar -d -f jaxb-api-2.3.0.jar
java.xml.bind jar:file:///C:/jaxb/mod/jaxb-api-2.3.0.jar/!module-info.class
exports javax.xml.bind
exports javax.xml.bind.annotation
exports javax.xml.bind.annotation.adapters
exports javax.xml.bind.attachment
exports javax.xml.bind.helpers
exports javax.xml.bind.util
requires java.activation transitive
requires java.base mandated
requires java.desktop
requires java.logging
requires java.xml transitive
uses javax.xml.bind.JAXBContextFactory

一番上の行がモジュール名とモジュラーJARファイルの場所を示しています。

はじめのrequires文で指定しているjava.activationがJAFのモジュールです。

ただし、JAFのjavax.activation-api-1.2.0.jarファイルはAUTOMATIC-MODULE-NAMEは記載されているものの、モジュールにはなっていません。このため、自動モジュールとして扱います。

他のrequires文はJava SEの標準なので、特に何もしなくても大丈夫です。

最後のuses文が前回も登場したServiceLoaderクラスを使用したSPIでロードするインタフェースです。そして、この実装クラスがあるのが、jaxb-impl.jarファイルです。(本当に使用するインタフェースと実装クラスは違うようなのですが、ここでは深入りしません)

jaxb-impl.jarファイルはモジュラーJARではないので、前回説明したようにクラスパスでもモジュールパスで指定してもどちらでも大丈夫です。

まずクラスパスで指定して実行してみましょう。

ここではmodディレクトリにnet.javainthebox.xmlモジュールのjaxbdemo.jarファイル、java.xml.bindモジュールのjaxb-api.jarファイル、JAFのjavax.activation-api-1.2.0.jarファイルを配置してあります。

クラスパスで指定するjaxb-impl.jarファイルとjaxb-core.jarファイルはlibファイルに置きます。

C:\jaxb>java -p mod -cp lib\jaxb-impl.jar;lib\jaxb-core.jar -m net.javainthebox.xml/net.javainthebox.JAXBDemo
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by com.sun.xml.bind.v2.runtime.reflect.opt.Injector (file:/C:/home/yuichi/Web/java/diary/material/201808/20180817jaxb/lib/jaxb-impl.jar) to method java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int)
WARNING: Please consider reporting this to the maintainers of com.sun.xml.bind.v2.runtime.reflect.opt.Injector
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
Yuichi Sakuraba

C:\jaxb>

実行することができました。

警告が出ているのはJAXBが内部でsun.misc.Unsafeクラスを使用しているためです。

次にモジュールパスで指定して、実行してみます。

jaxb-impl.jarファイルはmodディレクトリに移動してあります。

C:\jaxb>java -p mod -cp lib\jaxb-core.jar -m net.javainthebox.xml/net.javainthebox.JAXBDemo
Exception in thread "main" java.lang.NoClassDefFoundError: com/sun/xml/bind/v2/model/annotation/AnnotationReader
        at java.base/java.lang.ClassLoader.defineClass1(Native Method)
        at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1016)
        at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1095)
        at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:206)
        at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:760)
        at java.base/jdk.internal.loader.BuiltinClassLoader.findClassInModuleOrNull(BuiltinClassLoader.java:681)
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:606)
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:580)
        at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
        at java.base/java.lang.Class.getDeclaredMethods0(Native Method)
        at java.base/java.lang.Class.privateGetDeclaredMethods(Class.java:3167)
        at java.base/java.lang.Class.getMethodsRecursive(Class.java:3308)
        at java.base/java.lang.Class.getMethod0(Class.java:3294)
        at java.base/java.lang.Class.getMethod(Class.java:2107)
        at java.xml.bind/javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:295)
        at java.xml.bind/javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:286)
        at java.xml.bind/javax.xml.bind.ContextFinder.find(ContextFinder.java:409)
        at java.xml.bind/javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:721)
        at java.xml.bind/javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:662)
        at java.xml.bind/javax.xml.bind.JAXB$Cache.<init>(JAXB.java:127)
        at java.xml.bind/javax.xml.bind.JAXB.getContext(JAXB.java:154)
        at java.xml.bind/javax.xml.bind.JAXB.unmarshal(JAXB.java:168)
        at net.javainthebox.xml/net.javainthebox.JAXBDemo.main(JAXBDemo.java:12)
Caused by: java.lang.ClassNotFoundException: com.sun.xml.bind.v2.model.annotation.AnnotationReader
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:582)
        at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
        ... 24 more

C:\jaxb>

AnnotationReaderクラスがないという例外が発生してしまいました!

なぜ、クラスパスだと正常に実行できて、モジュールパスだと実行できないのでしょう?

 

答えはjaxb-impl.jarファイルをモジュールとして扱うか、普通のJARファイル(無名モジュール)として扱うかというところにあります。

ここでロードできないcom.sun.xml.bind.v2.model.annotation.AnnotationReaderクラスはjaxb-core.jarファイルに含まれています。

一方、jaxb-impl.jarファイルにもcom.sun.xml.bind.v2.model.annotationパッケージが含まれているのです。

Project Jigsawでは同じパッケージを複数のモジュールで定義することはできません。1つのパッケージは1つのモジュールで定義します。

jaxb-impl.jarファイルをモジュールパスに配置してしまうと自動モジュールとして扱うので、このパッケージに関する制限に引っかかってしまうのです。このため、jaxb-core.jarファイルのクラスはロードされなかったのです。

しかし、クラスパスで指定すればモジュールではないので、パッケージが複数のJARファイルに分かれていても問題ないわけです。

JAXBがjaxb-impl.jarファイルとjaxb-core.jarファイルに分かれていなければ、なんの問題もないのですが...

既存のライブラリでも同じように同じパッケージを複数のJARファイルに含んでいる場合があるかもしれません。クラスをロードできるはずなのに、ClassNotFoundException例外が発生するような場合は、パッケージが複数のJARに分かれている可能性が高いですね。

こういう問題は、ライブラリがちゃんとメンテされていれば、時間が解決してくれるとは思います。

それにしても、こういうことがあるので、ライブラリはともかく、アプリケーションであれば今すぐモジュール化する必要はないのではないでしょうか。

2018/08/14

Project Jigsawのちょっとしたクイズ

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

前回のエントリーでも触れましたけど、Jigsawのモジュールには通常のモジュールと自動モジュール (Automatic Module)、無名モジュール (Unnamed Module)の3種類あります。

自動モジュールと無名モジュールはモジュールとは名前がついてますけど、普通のJARファイルと変わりません。

自動モジュールはモジュールパスで指定し、無名モジュールは従来通りクラスパスで指定します。

通常のモジュールがアクセスできるのは、通常のモジュールか自動モジュールだけ。無名モジュールにはアクセスできません。無名モジュールにアクセスできるのは、自動モジュールです。

 

ところで、みなさんはjava.util.ServiceLoaderクラスをご存知でしょうか。

ServiceLoaderクラスを使うと、指定したインタフェースの実装クラスを実行時にロードすることができます。いわゆるSPIを実現するためのクラスです。

もちろん、JigsawでもServiceLoaderクラスをサポートしてますが、従来の方法とは実装クラスの指定方法が変わっています。

今までは、インタフェースの実装クラスを提供する場合、JARファイルのMETA-INF/servicesディレクトリにインタフェースと同名のファイルを作成し、ファイルには実装クラス名を記述します。

たとえば、インタフェースがnet.javainthebox.hello.Helloインタフェースで、SPIで提供する実装クラスがnet.javainthebox.hello.impl.HelloImplクラスだったとします。

この場合、META-INF/services/net.javainthebox.hello.Helloファイルを作成します。そして、net.javainthebox.hello.Helloファイルにはnet.javainthebox.hello.impl.HelloImplとだけ記述しておきます。

これでServiceLoaderは、クラスパスにあるJARファイルを調べて、Helloインタフェースの実装クラスをロードすることができました。

 

実装クラスをモジュールで提供する場合、インタフェースと同名のファイルを作成するのではなく、module-info.javaに記述します。

先ほどの例であれば、module-info.javaには次のように記述します。

module net.javainthebox.helloimpl {
    requires net.javainthebox.hello;
    
    provides net.javainthebox.hello.Hello with net.javainthebox.hello.impl.HelloImpl;
}

provides文でインタフェースと実装クラスを記述するわけです。

ただし、後方互換性のためにmodule-info.javaに記述するだけでなく、META-INF/servicesディレクトリにインタフェースと同名のファイルを置いておいた方がいいと思います。

モジュールがこの実装クラスを使いたい場合、はmodule-info.javaにuses文でインタフェースを指定します。

たとえば、次のように記述します。

module net.javainthebox.helloclient {
    requires net.javainthebox.hello;
 
    // SPIで使用するインタフェース
    uses net.javainthebox.hello.Hello;
}

ServiceLoaderクラスの使い方はまったく同じで、Helloインタフェースの実装クラスを探してロードすることができます。

 

ここでクイズです。

インタフェースは通常のモジュールで定義されています。インタフェースを使用するクライアントもモジュールです。

しかし、実装クラスがモジュールでない場合、ようするにMETA-INF/servicesディレクトリを使ったJARファイルの場合、どうすれば実装クラスを読み込むことができるでしょうか。

選択肢は4つ。

  1. 実装クラスがモジュールでないので、読み込めない
  2. モジュールパスで指定したディレクトリに実装クラスのJARファイルを配置して、自動モジュールとして読み込む
  3. クラスパスで指定して、無名モジュールとして読み込む
  4. モジュールパスでもクラスパスでも、どちらでもOK

 

正解は.....

 

選択肢4のモジュールパスでもクラスパスでもOKです。

通常のモジュールからのアクセスなので、モジュールパスに配置して自動モジュールとして扱わなくてはいけないように思えるかもしれません。でも、クラスパスでもOKなんです。

通常のモジュールが無名モジュールにアクセスできるという稀有な例なのでした。

 

いちおう、コードと実行例を示しておきます。

SPIで使用するHelloインタフェースはこんな感じ。

package net.javainthebox.hello;

public interface Hello {
    public void hello();
}

module-info.javaは次の通り。

module net.javainthebox.hello {
    exports net.javainthebox.hello;
}

実装クラスのHelloImplクラス。

package net.javainthebox.hello.impl;

import net.javainthebox.hello.Hello;

public class HelloImpl implements Hello {
    public void hello() {
        System.out.println("Hello, World!");
    }
}

HelloImplクラスを含んだJARファイルは、前述のようにMETA-INF/services/net.javainthebox.hello.Helloファイルを作成して、net.javainthebox.hello.impl.HelloImplと記述してあります。

さて、クライアントは。

package net.javainthebox.helloclient;

import java.util.ServiceLoader;
import net.javainthebox.hello.Hello;

public class HelloClient {
    public static void main(String... args) {
        ServiceLoader<Hello> loader = ServiceLoader.load(Hello.class);

        for (Hello hello: loader) {
            hello.hello();
        }
    }
}

クライアントのmodule-info.javaは上の方に書いてありますね。

ビルドはやってもらうということで、実行してみます。

modディレクトリをモジュールパス、libディレクトリをクラスパス用に使用するとしましょう。

まずは、自動モジュールとして実行してみます。

C:\serviceclient>dir mod
 ドライブ C のボリューム ラベルがありません。
 ボリューム シリアル番号は 4A4B-822F です

 C:\serviceclient\mod のディレクトリ

2018/08/14  21:58    <DIR>          .
2018/08/14  21:58    <DIR>          ..
2018/08/12  14:36             1,235 hello-api.jar
2018/08/12  20:01             1,603 hello-client.jar
2018/08/12  19:36             1,607 hello-impl.jar
               3 個のファイル               4,445 バイト
               2 個のディレクトリ  163,930,116,096 バイトの空き領域

C:\serviceclient>java -p mod -m net.javainthebox.helloclient/net.javainthebox.helloclient.HelloClient
Hello, World!

C:\serviceclient>

Hello, World!が表示されました。

次に、実装クラスのJARファイルをlibディレクトリに移動させて、クラスパスで指定してみましょう。

C:\serviceclient>mv mod\hello-impl.jar lib

C:\serviceclient>java -p mod -cp lib\hello-impl.jar -m net.javainthebox.helloclient/net.javainthebox.helloclient.HelloClient
Hello, World!

C:\serviceclient>

クラスパスで指定しても、ちゃんと実行できています。

というわけで、ちょっとしたJigsawのクイズでした。