Java12からJava15までのJavaVMへの主な変更点
開発者や実行するアプリケーションを最適化するため、JavaVMに関する追加・変更もありました。特に最近では、マイクロサービスなどを実現するために、フレームワークもより軽量でかつ、配布しやすい形が望まれてきています。そして、サーバレスアーキテクチャという需要も高まりつつあるなかで、それらの影響がJavaの世界にもおよびつつあります。
これらの変更はこれまでとJavaの適用範囲が変わってきたことも想定していると思われ、開発者だけではなく、Javaアプリケーションを運用する方も知っておくとよい追加・変更点を紹介します。
| JEP | 名称 | リリースされたバージョン |
|---|---|---|
| JEP358 | Helpful NullPointerExceptions | Java14 |
| JEP371 | Hidden Classes | Java15 |
| JEP372 | Remove the Nashorn JavaScript Engine | Java15 |
| JEP341 | Default CDS Archives | Java12 |
| JEP350 | Dynamic CDS Archives | Java13 |
| JEP343 | Packaging Tool | Java14(Incubator) |
| JEP372 | Remove the Nashorn JavaScript Engine | Java15 |
Helpful NullPointerExceptions - NullPointerExceptionの理由がわかりやすくなった
まず、Java開発者やJavaのアプリケーション運用者によい影響である、ちょっとした例外メッセージに関する変更点を紹介します。
Javaのアプリケーションでよく見かけるが、原因がよくわからない例外の1つにNullPointerExceptionがあります。プログラム時に考慮されていれば、もともと生じないはずのエラーであるゆえ、実際に生じるとなかなか原因がわかりません。特に、最近ではよりシンプルに記述しやすいスタイルが好まれるようになり、1行で多くの処理をしているコードをよく見かけるようになりました。しかし、1行で記述しているがゆえに、その行でNullPointerExceptionが発生すると具体的に何がNULLなのかわからないということがありました。今回の改善ではエラーメッセージが改善されているので、実際のエラー原因がよりわかりやすくなっています。
例えば、リスト1のようなコードを使って、実際にNullPointerExceptionを発生させたとします。
public void case1(){
Point p = new Point();
int xx = p.child.child.x; // ここでNullPointerExceptionを発生される
}
public static class Point {
private int x;
private int y;
private Point child;
}
Java11などこれまでは、リスト2のようにエラーが発生している行数まではわかっても、実際にどのオブジェクトがないためにエラーが発生しているのかがわかりませんでした。そのため、このように1行でオブジェクトの参照をたどるようなコードはJava開発者が好んでも、Javaアプリケーション運用者からは好まれない傾向がありました。
Exception in thread "main" java.lang.NullPointerException
at jp.enbind.jdk15.Jep358.case1(Jep358.java:19)
at jp.enbind.jdk15.Jep358.main(Jep358.java:14)
今回の改善では、リスト3のように表示されるので、実際にどこでエラーが発生しているかがわかります。
Exception in thread "main" java.lang.NullPointerException: Cannot read field "child" because "p.child" is null
at jp.enbind.jdk15.Jep358.case1(Jep358.java:19)
at jp.enbind.jdk15.Jep358.main(Jep358.java:14)
ちょっとしたメッセージの変更ですが、こういったエラーメッセージの変更は多くの方にとってメリットがあります。
Hidden class - 一般開発者からは見えないクラス定義が可能
通常のプログラミングをしていると意識しない部分ですが、Java15からは通常の開発者からは使用できない隠しクラスが作れる機能が導入されます。例えば、Javaを使ったミドルウェアやフレームワークなどの開発を行っている方は、Javaのバイトコードを実行時に書き換えたり、作成したりする場合があります。
これらのクラスは、意図的に一般の開発者からは見えないようにしたいときもありますが、ClassLoaderなどにアクセスすると見えてしまう場合があります。これまでも、sun.misc.Unsafe.defineAnonymousClassを使うことで同様のことができましたが、このようなJDKに依存する内部APIを利用することは非推奨であり、将来的には削除されます。
そのため、Java15からは、java.lang.invoke.MethodHandles.LookupクラスにdefineHiddenClassメソッドが追加され、そちらでHiddenクラスが作成可能になります。例えば、簡単なHiddenクラスを作成するコードがリスト4です。
public void case1() {
try {
//(1) クラスのバイトコードを取得
byte classBytes[] = toBytes(Sample.class);
//(2)Hiddenクラスとして登録する
MethodHandles.Lookup lookup = MethodHandles.lookup();
Class clz = lookup.defineHiddenClass(classBytes,true).lookupClass();
//(3)登録したHiddenクラスからインスタンスを作成する
Object obj = clz.getDeclaredConstructor(null).newInstance();
ISample sample = (ISample)obj;
sample.invoke();
//(4)java.lang.ClassNotFoundException が発生
Class clz2 = this.getClass().getClassLoader().loadClass(clz.getName());
}
catch(Exception ex){
logger.log(System.Logger.Level.INFO,"error {0} - {1}",ex.getClass(),ex.getMessage());
ex.printStackTrace();
}
}
public byte[] toBytes(Class clz) throws IOException{
// (省略)
}
public interface ISample{
void invoke();
}
public static class Sample implements ISample{
@Override
public void invoke() {
System.out.println("invoked");
}
}
(1)はクラスのバイトコードを取得するコードです。実際には、Javassistのようなライブラリを使ってバイトコードの書き換えやその他、一般の開発者には見せたくないバイトコードのデータを用意します。
そして、(2)でMethodHandles.Lookupを取得し、そのdefineHiddenClassメソッドを使いHiddenクラスとして登録します。実際に登録したクラスを利用する場合には、(3)のように登録した際に取得可能なClassオブジェクトを利用します。登録したクラスは(4)のようにClassLoaderからClassオブジェクトを取得しようとしても見つけることはできません。
