※本記事は、Raoul-Gabriel Urmaによる”Java 14 Arrives with a Host of New Features“を翻訳したものです。
これまでの2回のリリースを超える新機能が搭載されたJava 14は、コーディングを簡単にする新機能が満載
著者:Raoul-Gabriel Urma
2020年2月27日
Java 14は、3月17日にリリースされる予定です。バージョン14には、Java 12と13を合わせたものよりも多くのJEP(Java拡張提案)が搭載されています。それでは、その内容はどのようなもので、日々コードを書き、そのメンテナンスを行っているJava開発者にとってもっとも関連があるものは何でしょうか。
本記事では、以下に示す大きな進展について説明します。
- Java 12でプレビュー機能として初登場し、Java 13を経て改善され、Java 14で正式機能となったswitch式
- instanceofのパターン・マッチング(言語機能)
- 便利なNullPointerException(JVM機能)
本記事を読んで、これらの機能を自分のコードベースで試してみる場合は、Javaチームにフィードバックを提供し、経験を共有することをお勧めします。そうすることで、Javaの発展に貢献する機会が得られます。
switch式
Java 14で、switch式は恒久版になりました。switch式がどんなものであるかを忘れてしまった方は、以前の2つの特集記事をお読みください。
以前のリリースでは、switch式は「プレビュー」機能でした。ご存じの方もいらっしゃると思いますが、「プレビュー」機能はフィードバックを集めるためのもので、その内容によって変更されることや、場合によっては削除される可能性があるものです。しかし、「プレビュー」機能のほとんどはやがて正式にJavaの一部になることが想定されています。
新しいswitch式のメリットには、フォールスルー動作がないためバグが発生しにくい、網羅性がある、式と複合条件のおかげで書きやすくなっている、などがあります。switch式では、アロー構文が使えるようになっています。次の例を見て思い出してください。
var log = switch (event) {
case PLAY -> "User has triggered the play button";
case STOP, PAUSE -> "User needs a break";
default -> {
String message = event.toString();
LocalDateTime now = LocalDateTime.now();
yield "Unknown event " + message +
" logged on " + now;
}
};
テキスト・ブロック
Java 13では、プレビュー機能としてテキスト・ブロックが導入されました。テキスト・ブロックを使うことで、複数行文字列リテラルを扱いやすくなります。この機能は、Java 14でプレビューの第2ラウンドを迎えることになり、いくつかの微調整が行われています。少しばかり復習しておきますが、しかるべき複数行テキストを表現する場合、多くの文字列結合とエスケープ・シーケンスを使用してコードを書くのが至って普通でした。以下のコードは、HTML形式のテキストを組み立てる例です。
String html = "<HTML>" + "\n\t" + "<BODY>" + "\n\t\t" + "<H1>\"Java 14 is here!\"</H1>" + "\n\t" + "</BODY>" + "\n" + "</HTML>";
テキスト・ブロックにより、この作業を簡略化し、テキスト・ブロックの最初と最後を表す3つの二重引用符を使って美しいコードを書くことができます。
String html = """
<HTML>
<BODY>
<H1>"Java 14 is here!"</H1>
</BODY>
</HTML>""";
テキスト・ブロックは、通常の文字列リテラルと比較して、表現力もかなり向上しています。この点の詳細については、以前の記事をご覧ください。
Java 14では、2つの新しいエスケープ・シーケンスが追加されました。1つ目として、単一の空白を示す新しいエスケープ・シーケンス\sを使用できます。2つ目に、行末に改行文字が挿入されるのを防ぐための方法として、バックスラッシュ\を使用できます。このバックスラッシュは、非常に長い行があり、テキスト・ブロック内部の可読性を向上させるために分割したい場合に便利です。
例として、複数行文字列を扱う、現在の方法を示します。
String literal =
"Lorem ipsum dolor sit amet, consectetur adipiscing " +
"elit, sed do eiusmod tempor incididunt ut labore " +
"et dolore magna aliqua.";
テキスト・ブロックにエスケープ・シーケンス\を含めることで、上記の内容を次のように表現できます。
String text = """
Lorem ipsum dolor sit amet, consectetur adipiscing \
elit, sed do eiusmod tempor incididunt ut labore \
et dolore magna aliqua.\
""";
instanceofのパターン・マッチング
Java 14には、プレビュー機能として、instanceofによる条件チェック後に明示的キャストを行わなくてよくなる機能が導入されています。たとえば、次のコードについて考えてみます。
if (obj instanceof Group) {
Group group = (Group) obj;
// use group specific methods
var entries = group.getEntries();
}
今回のプレビュー機能を使用して、上記のコードを次のようにリファクタリングすることができます。
if (obj instanceof Group group) {
var entries = group.getEntries();
}
条件チェックによってobjがGroup型であることが明らかになるため、最初のスニペットの条件ブロックのように、objがGroup型であると再度宣言する必要はありません。この宣言は、エラーが紛れ込む可能性を高めるものです。。
このように短くなった構文では、一般的なJavaプログラムに含まれる多くのキャストが除去されます(ある関連言語機能を提案した2011年の研究論文では、すべてのキャストのおよそ24 %が、条件文に含まれるinstanceofより後に登場していることが報告されました)。
この変更を扱っているJEP 305では、Joshua Bloch氏の書籍『Effective Java』に掲載されている例に注目しており、等価性を評価する次のメソッドを使用して説明されています。
@Override public boolean equals(Object o) {
return (o instanceof CaseInsensitiveString) &&
((CaseInsensitiveString) o).s.equalsIgnoreCase(s);
}
上記のコードは、CaseInsensitiveStringへの冗長な明示的キャストを削除して、次のような形に縮小することができます。
@Override public boolean equals(Object o) {
return (o instanceof CaseInsensitiveString cis) &&
cis.s.equalsIgnoreCase(s);
}
これによってさらに汎用的なパターン・マッチングへの扉が開くことから、興味深く実験できるプレビュー機能です。パターン・マッチングの考え方は、一定の条件に基づいてオブジェクトのコンポーネントを抽出する便利な構文を持つ言語機能を提供するというものです。これはinstanceof演算子にも当てはまります。パターンマッチの条件が型チェックであり、抽出は、しかるべきメソッドの呼出しまたは固有のフィールドへのアクセスであるからです。
すなわち、このプレビュー機能は始まりにすぎません。さらに冗長性を減らすことでバグ発生の可能性を低下させる言語機能に期待できるということです。
レコード
もう1つのプレビュー言語機能がレコードです。これまでに浮上してきた他の考え方と同じように、この機能も、Javaの冗長性を減らして開発者がより簡潔なコードを書けるようにするというトレンドに従っています。レコードは、フィールドにデータを保存することだけを目的とし、カスタム動作は一切宣言していないドメイン・クラスを対象にしています。
簡単なドメイン・クラスBankTransactionを例に、直接問題に踏み込んでみることにします。このクラスは、date(日付)、amount(金額)、description(説明)という3つのフィールドで取引をモデリングしたものです。クラスを宣言するときは、複数のコンポーネントについて考える必要があります。
- コンストラクタ
- getterメソッド
- toString()
- hashCode()とequals()
多くの場合、このようなコンポーネントのコードはIDEで自動的に生成され、そのコードで多くの領域が占められます。生成されたBankTransactionクラスのすべての実装を示します。
public class BankTransaction {
private final LocalDate date;
private final double amount;
private final String description;
public BankTransaction(final LocalDate date,
final double amount,
final String description) {
this.date = date;
this.amount = amount;
this.description = description;
}
public LocalDate date() {
return date;
}
public double amount() {
return amount;
}
public String description() {
return description;
}
@Override
public String toString() {
return "BankTransaction{" +
"date=" + date +
", amount=" + amount +
", description='" + description + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BankTransaction that = (BankTransaction) o;
return Double.compare(that.amount, amount) == 0 &&
date.equals(that.date) &&
description.equals(that.description);
}
@Override
public int hashCode() {
return Objects.hash(date, amount, description);
}
}
Java 14では、冗長性を省き、必要なのはデータを集めるためだけのクラス(equals、hashCode、toStringの各メソッドの実装を含むもの)であるという意図を明確にする方法が提供されます。BankTransactionは、次のようにリファクタリングすることができます。
public record BankTransaction(LocalDate date,
double amount,
String description) {}
レコードを使うことで、コンストラクタとgetterの実装に加え、equals、hashCode、およびtoStringの実装が「自動的」に生成されます。
この例を試す際は、プレビュー・フラグを使ってファイルをコンパイルする必要があることを思い出してください。
javac --enable-preview --release 14 BankTransaction.java
レコードのフィールドは、暗黙的にfinalになります。つまり、再代入はできません。ただし、レコード全体が不変という意味ではない点に注意してください。フィールドに格納するオブジェクト自体は可変でも構いません。
レコードについてさらに詳しく知りたい方は、Ben Evans氏が最近執筆したJava Magazineの記事をご覧ください。
この機能には期待できます。レコードは、次世代のJava開発者に対する教育的観点から、興味深い質問をもたらすものでもあります。たとえば、若い開発者を指導する場合、レコードはカリキュラムのどこで説明するべきでしょうか。OOPやクラスを紹介する前がよいでしょうか。それとも、その後がよいでしょうか。
便利なNullPointerException
NullPointerExceptionのスローを、Javaの新たな「Hello world」にすべきだという意見があります。。この例外から逃れることはできないからです。冗談はさておき、この例外はコードが本番環境で実行されているときにアプリケーションのログにたびたび現れては、フラストレーションを引き起こします。元々のコードをすぐに見ることはできないため、デバッグが難しい場合もあります。たとえば、次のコードがソースの5行目にある場合を考えてみます。
var name = user.getLocation().getCity().getName();
Java 14より前の場合、次のようなエラーが発生することがあります。
Exception in thread "main" java.lang.NullPointerException
at NullPointerExample.main(NullPointerExample.java:5)
残念ながら、5行目を見ても、複数のメソッド呼出し、すなわちgetLocation()とgetCity()が割り当てられています。そのいずれかがNULLを返している可能性があります。実際には、変数userがNULLである可能性もあります。つまり、何がNullPointerExceptionを引き起こしているかは明確ではありません。
Java 14には、詳細な診断を受け取ることができる新しいJVM機能が追加されました。
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "Location.getCity()" because the return value of "User.getLocation()" is null
at NullPointerExample.main(NullPointerExample.java:5)
JVMのメッセージに、2つの明確な要素が含まれるようになりました。
- 結果:Location.getCity()を呼び出すことができない
- 理由:User.getLocation()の戻り値がNULLである
この拡張診断は、次のフラグを使ってJavaを起動した場合にのみ有効になります。
-XX:+ShowCodeDetailsInExceptionMessages
次に例を示します。
java -XX:+ShowCodeDetailsInExceptionMessages NullPointerExample
こちらに記されているように、将来のバージョンのJavaではこの機能がデフォルトで有効になる可能性があります。
この機能拡張は、メソッド呼出しに対してだけ有効になるわけではなく、NullPointerExceptionが発生する可能性のあるその他の場所(フィールドへのアクセス、配列へのアクセス、代入など)でも動作します。
まとめ
Java 14には、開発者の日々の仕事に役立つ新しいプレビュー言語機能やアップデートが搭載されています。たとえば、Java 14では、明示的キャストを減らすための方法であるinstanceofパターン・マッチングが導入されています。さらに、データを集めるだけのクラスを簡潔に宣言する新しい構造であるレコードも導入されています。加えて、NullPointerExceptionのメッセージが拡張されて詳しい診断情報が含まれるようになり、switch式が正式にJava 14の一部になっています。複数行文字列値を扱う際に役立つテキスト・ブロック機能は、2つの新しいエスケープ・シーケンスが導入されたうえで、プレビューの第2ラウンドに入っています。他の変更点の中で、Javaを運用する技術者の一部にとって興味深いものが、JDK Flight Recorderのイベント・ストリーミングです。このオプションは、Ben Evans氏によるこちらの記事で説明されています。
おわかりのように、Java 14にはイノベーションが満載されています。ぜひJava 14を試し、プレビュー機能に関するフィードバックをJavaチームにお寄せください。
![]() |
Raoul-Gabriel UrmaRaoul-Gabriel Urma(@raoulUK):イギリスのデータ・サイエンティストや開発者の学習コミュニティをリードするCambridge SparkのCEO/共同創業者。若いプログラマーや学生のコミュニティであるCambridge Coding Academyの会長/共同創設者でもある。ベストセラーとなったプログラミング関連書籍『Java 8 in Action』(Manning Publications、2015年)の共著者として執筆に携わった。ケンブリッジ大学でコンピュータ・サイエンスの博士号を取得している。 |

