try-with-resources文の改善
try-with-resources文とは、必ず閉じなければならないリソースについて文の終わりで必ず閉じられるようにするための構文です。
この構文を使わない場合や、Java 7より以前からのコードになれている方は、finallyでclose()メソッドを必ず実行していたはずです。しかし、Java 7からこの構文が導入されたために、finallyでの記述が必要なくなり、そしてJava 9では、さらに直感的に記述ができるようになりました。実際にJava 7以前とJava 7以降、そしてJava 9での記述可能な方法を示したのが、リスト7です。
// (1) Java 7以前
SampleReader r1 = new SampleReader();
try{
r1.read();
}
catch(Exception ex){
r1.delete();
}
finally {
try {
r1.close();
}
catch(Exception ex){
}
}
// (2) Java 7以降
try(SampleReader r7 = new SampleReader()){
r7.read();
}
catch(Exception ex){
// r7.delete(); 実行できない
}
// (3) Java 9以降
SampleReader r9 = new SampleReader();
try(r9){
r9.read();
}
catch(Exception ex){
r9.delete();
}
public class SampleReader implements AutoCloseable{
public void read(){
throw new RuntimeException("error");
}
// エラーが発生した場合にのみ実行したい処理
public void delete(){
System.err.println("delete()");
}
@Override
public void close() throws IOException {
System.err.println("close()");
}
}
今回のサンプルでは簡単なSampleReaderというAutoCloseableインターフェースを実装したクラスを使って確認しています。このクラスでは問題がわかりやすいように、readメソッドでは必ずエラーが発生するようにしています。
(1)はJava 7以前の記述方法でエラーが発生しても必ず実行できるように、finally内にcloseメソッドで実行していました。この記述をしないと、closeメソッドが呼ばれないためリソースを正しく解放できません。
(2)はJava 7からの記述方法です。try文の中で宣言したリソースだけが自動的にcloseされる対象です。しかし、これでは、try文のスコープ外では利用できないため、例えば、catchやfinally内で特別に別の処理をしたくても利用することができません。今回のサンプルのようにエラーが発生した場合には、delete()メソッドを実行したくてもスコープ外のため記述できません。
そこでJava 9では、(3)のように変数の宣言をtry文のスコープ外で宣言していても、try文でそのリソースを指定すると自動でリソースを解放してくれるようになりました。
Javaベースモジュール内で利用できるLogger
Javaにはすでにjava.util.loggingというパッケージがあり、ログについてはこちらで実装されています。では、今回新規に用意されたSystem.Loggerは何が違うのでしょうか。
例えば、SLF4Jなどを使っている方にとってはわかりやすいと思います。SLF4Jでは、ログの具体的な実装については提供していないため、どのログ実装を使っていても、ログを出力する側のプログラムでは同じように記述できます。
具体的なログ出力のための実装に何を使うかは、実行時に決めることができます。System.Loggerは、JDK標準のロギングのためのインターフェースです。既存のサードパーティー製のライブラリも、今後、この機構を使って実装されるようになっていくと思います。
実際にログを記述する際のコードを示したのがリスト8です。
System.Logger logger = System.getLogger("logname");
logger.log(System.Logger.Level.INFO,"system log");
// 以下、出力例
// 2月 17, 2018 1:36:39 午後 com.coltware.part6.jep264.SystemLog check
// 情報: system log
ログ出力のための実装はサービス機構を使って提供する必要がありますが、特に何も指定していない場合、java.util.loggingのAPIが利用されます。
自分でロギング実装を提供するために必要なコードを示したのがリスト9です。
// (1) 利用するログクラスを指定するクラスを作成する
public class SampleLoggerFinder extends System.LoggerFinder {
@Override
public System.Logger getLogger(String name, Module module) {
return new SampleLogger(name);
}
}
// (2) module-info.javaで作成したクラスをサービスとして利用できるように指定する
uses System.LoggerFinder;
provides System.LoggerFinder with SampleLoggerFinder;
(1)ではSystem.LoggerFinderクラスを継承したクラスを作成し、その中で自分で作成、もしくは外部ライブラリなどSystem.Loggerインターフェースを実装したクラスを返すように指定します。
続いて、サービスとしてそのクラスが利用できるように、module-info.java内でSystem.LoggerFinderを利用する宣言と、そのSystem.LoggerFinderの実装としてSampleLoggerFinderを利用する宣言をします。
その他の注目すべき改善
ここからは、その他の注目すべき改善点を解説します。
まず、プロパティファイル(ResourceBundle)として、UTF-8などの文字コードで記述できるようになり、この変更ともにデフォルトのファイルのエンコーディングが ISO-8859-1からUTF-8に変更されました。この問題はすでにフレームワークなどを使っている方は、それらのフレームワークで個別の対策を採っている場合もありますが、標準で問題なく利用できるようになりました。
また、それ以外としては、String内部のデータ保持形式が変わりました。これまでは1文字につき、2byteでデータを管理していましたが、Latin-1のように1byteで管理できるコードのみの場合には、1byteで管理するようになっています。これは、通常の利用では1byte管理で問題ないということが大きな理由です。ただし、APIとしての振る舞いは変わりませんので、これまでのコードに影響はありません。また、日本語データのような場合には、同様に2byteで管理されます。
細かい部分での消費メモリの改善は開発者にとってはあまり大きくない変更ともいえますが、ハードウェアやOSへの最適化、もしくは、メモリ効率改善の流れは、Java10以降での「Project Valhalla」や「Project Panama」というプロジェクトにてより本格的な改善が行われる予定です。そのような流れがわかるひとつのトピックスとして、今回のString内部の改善を捉えるとよいでしょう。
最後に
本連載では第2回以降、Javaのコーディングを中心にJava 9の変更点について紹介してきました。モジュール機能という大きな追加要素に注目しがちですが、細かく既存の部分の改善も行われていることがわかります。
次回はJavaの実行環境面での変更点である、jlinkコマンドとMulti-Release Jarファイルを中心に紹介します。また、あえて主観的な視点で筆者が感じるJavaについての現在の流れや、今後について解説する予定です。
