キーポイント
- Java 17, the newest long-term support release of Java, was made available to the Java community in September 2021.
- All the features reviewed here have officially been added to the language, having completed their "preview" phases.
- These features are purely syntactic, all being new types of classes and keywords. There are many more changes worth researching!
- Although very cutting-edge, Java 17 is a complete and stable version of Java, and is definitely worth learning or even migrating your applications to this version.
2021年9月、Oracle は Java の次の長期サポートリリースである Java 17 を公開しました。主に Java 8 または Java 11 で仕事をしている場合、Java 12 以降の優れた追加機能のいくつかに気付いていないかもしれません。
これは重要なリリースであり、個人的に興味をそそられた新機能のいくつかをハイライトすると良いのではと思いました。
注意、Java のほとんどの変更は最初に「プレビュー」されます。つまり、リリースに追加されていますが、それは、まだ完了したとは見なされていません。それらは実験することが奨励されていますが、プロダクションコードで使用することは奨励されていません。
私が選んだすべての機能は正式に Java に追加されたもので、プレビューフェーズを過ぎています。
1: Sealed Classes (シールドクラス)
Java 15 でプレビューされ、Java 17 で正式に追加された Sealed Classes は、継承に関するルールを適用する新しい手段です。キーワード sealed をクラスまたはインターフェースの定義に追加すると、それを拡張 (extend) または実装 (implement) することができるクラスのリストも追加されます。たとえば、次のように定義されたクラスを作成した場合:
public abstract sealed class Color permits Red, Blue, Yellow
その場合、Red、Blue
と Yellow
のクラスだけが拡張できます。事前に定義されたもの以外はコンパイルに失敗します。
キーワード permits
を完全に省略して、すべてのシールドクラス定義をクラス自体と同じファイルに次のように保持することもできます:
public abstract sealed class Color {...}
... class Red extends Color {...}
... class Blue extends Color {...}
... class Yellow extends Color {...}
これらのサブクラスは、シールドクラスにネストされているのではなく、クラス定義の後にあることに注意してください。これは、permits
キーワードを使用する場合と機能的に同じです。Color
を拡張できるクラスは、依然として Red、Blue
と Yellow
だけです。
では、シールドクラスはどう役立つのでしょうか? 継承に関するルールを適用することで、カプセル化のルールも適用することになります。ソフトウェアライブラリを構築していて、抽象クラス class
Color
が必要な場合を考えてみましょう。Color
とは何か、そしてそれを拡張するために何が必要かを知っていて、それが public として宣言されている場合、外部コードがそれを拡張するのを防ぐものは何でしょうか?
誰かがその目的を誤解して Square に拡張した場合はどうなるでしょうか? これはあなたの意図したことでしょうか? または、Color を内部に保持したいでしょうか? それでも、パッケージレベルの可視性ですべての問題を防ぐわけではありません。誰かが後でライブラリを拡張した場合はどうなるでしょうか? 彼らは、あなたがクラスの小さなサブセットに対してだけの Color を意図していることをどうやったらわかるのでしょうか?
シールドクラスは、外部ソースからコードを保護するだけでなく、決して会わないかもしれない人々に意図を伝えます。クラスが封印されている場合、これらを拡張することを目的としたクラスであり、他のクラスはそうではないと言うことになります。これは興味深い種類の堅牢性であり、数年後には、コードを読む人なら誰でも、あなたが書いたものの厳密さを理解できるようになります。
2: Helpful Null Pointers (助けになる Null Pointer)
Helpful Null Pointers は興味深い更新です – 過度に複雑なものではありませんが、それでも歓迎すべき言語への変更です。Java 14 で正式に提供された Helpful Null Pointers は、null ポインタ例外 (NPE) の読みやすさを改善し、例外をスローした呼び出しの名前と null 変数の名前を出力します。たとえば、 a.b.getName()
を呼び出し、b が未定義の場合、例外のスタックトレースは、b が null であるために、getName()
が失敗したことを表示します。
みんなが通ってきた道です。NPE はとても一般的な例外であり、ほとんどの場合、原因を特定することは難しくありませんが、2つか3つの変数が原因となる場合があります。そのため、デバッグに飛び込んでコードのクロールを開始すると、問題の再現でトラブルが発生する可能性があります。そして、NPE を発生させた最初の場所が何かを覚えておかなければなりません。
事前に情報を得られれば、この面倒なすべてのデバッグ作業を行う必要はありません。ここで、Helpful Null Pointers が輝きます。NPE を推測する必要はもうありません。シナリオを再現するのが難しい場合でも、つかんだ瞬間、例外が発生するとすぐに問題を解決するために必要なすべてが揃います。
これは絶対的なライフセーバーになります!
3: Switch Expressions (スイッチ式)
少しの間我慢してください – Switch Expressions (Java 12 でプレビューされ、Java 14 で正式に追加された) は、やや switch 文とラムダの融合です。実際、私が初めてある人にスイッチ式を説明したとき、私はスイッチをラムダ化したと言いました。いくつかのコンテキストについて、構文を見てください:
String adjacentColor = switch (color) {
case Blue, Green -> "yellow";
case Red, Purple -> "blue";
case Yellow, Orange -> "red";
default -> "Unknown Color";
};
言ったとおりでしょう?
明らかな違いの1つは、break がないことです。スイッチ式は、Oracle が Java の冗長性を減らすトレンドを続けています。つまり、Oracle は、ほとんどのスイッチ文が CASE BREAK CASE BREAK CASE BREAKのように続くことを本当に嫌っていました。
そして正直なところ、ヒューマンエラーを非常に受けやすいため、それを嫌うのは正しいことでした。私たちの中で、実行時にコードがおかしくなったとき単にアラートが表示されて、switch の break 文を忘れていないと言うことができる人はいますか? スイッチ式は、同じブロックに含まれるすべての値をコンマで区切ることで、これを楽な方法で修正します。そうです、もう break はありません! 今ではそれ自体が処理します!
スイッチ式のもう1つの重要な部分は、新しい yield
キーワードです。ケースの1つがコードのブロックにフィードする場合、yield
がスイッチ式の return ステートメントとして使用されます。たとえば、上述のコードブロックを少し変更すると、次のようになります:
String adjacentColor = switch (color) {
case Blue, Green -> "yellow";
case Red, Purple -> "blue";
case Yellow, Orange -> "red";
default -> {
System.out.println("The color could not be found.");
yield "Unknown Color";
}
};
default ケースでは、yield 式が返すため、System.out.println()
メソッドが実行され、adjacentColor
変数は「UnknownColor」のままになります。
全体として、スイッチ式ははるかにすっきりとして、はるかに凝縮されたスイッチ文になります。そうは言っても、スイッチ文を置き換えるものではなく、どちらも引き続き使用できます。
4: Text Blocks (テキストブロック)
Java 13 で最初にプレビューされ、Java 15 で正式に追加された Text Blocks は、複数行の文字列の記述を簡素化し、新しい行を解釈し、エスケープ文字を必要とせずにインデントを維持します。テキストブロックを記述するには、次の構文を使用するだけです:
String text = """
Hello
World""";
この値も String ですが、新しい行とタブが含まれていることに注意してください。同様に、引用符を使用する場合は、エスケープ文字は必要ありません:
String text = """
You can "quote" without complaints!"""; // You can "quote" without complaints!
バックスラッシュによるエスケープが必要になるのは、リテラル """ を書き込もうとした場合だけです:
String text = """
The only necessary escape is \""",
everything else is maintained.""";
さらに、記述した内容に対して String の format() メソッドを直接呼び出すことができます。これにより、テキストブロックの情報を動的な値に簡単に置き換えることができます:
String name = "Chris";
String text = """
My name is %s.""".format(name); // My name is Chris.
テキストブロック固有のエスケープ文字である「\s」を指定しない限り、末尾のスペースも行ごとにクリップされます:
String text1 = """
No trailing spaces.
Trailing spaces. \s""";
では、どのような状況でテキストブロックを使用すればよいのでしょうか? テキストブロックを使用すると、単語の大きなブロックの書式を視覚的に焼き付けることができるだけでなく、コードのスニペットを文字列に貼り付けるのが非常に簡単になります。インデントが保持されるため、実際 HTML や Python、または他の言語をブロックを作成する場合、通常どおりに """ で囲むだけで記述できます。フォーマットを保持するために必要なのはこれだけです。テキストブロックを使用してJSONを記述したり、format()
メソッドを使用して値を簡単にプラグインしたりすることもできます。
全体として、非常に便利な変更です。テキストブロックは小さな機能のように見えますが、生活の質が長期的に向上します。
5: Record Classes (レコードクラス)
Java 14 でプレビューされ、Java 16 で正式に追加された Records は、データ専用クラスの POJO に関連付けられたすべてのボイラープレートコードを処理します。つまり、次のようにレコードを宣言した場合:
public record Coord(int x, int y) {
}
次に、equals()
メソッドと hashcode()
メソッドが実装され、toString()
はすべてのレコードの値の明瞭な出力を返します。最も重要なのは、x()
と y()
がそれぞれ x
と y
の値を返すメソッドになります。これまでに見た中で最も醜い POJO について考え、レコードで実装することを想像してください。それはどれくらい良く見えるでしょうか? それはどれだけ浄化効果があるでしょうか?
さらに、レコードは final でイミュータブル (不変) で レコードの拡張はありません。また、レコードオブジェクトが作成されると、そのフィールドを変更することはできません。レコードには非静的 (non-static)、静的 (static) 両方のメソッドを宣言できます。
public record Coord(int x, int y) {
public boolean isCenter() {
return x() == 0 && y() == 0;
}
public static boolean isCenter(Coord coord) {
return coord.x() == 0 && coord.y() == 0;
}
}
また、レコードは複数のコンストラクターを持つことができます:
public record Coord(int x, int y) {
public Coord() {
this(0,0); // The default constructor is still implemented.
}
}
レコードでカスタムコンストラクタを宣言するときは、デフォルトのコンストラクタを呼び出す必要があることに注意してください。そうしないと、レコードはその値をどう処理するかがわかりません。デフォルトに一致するコンストラクタを宣言する場合は、レコードのすべてのフィールドも初期化する限り問題ありません。
public record Coord(int x, int y) {
public Coord(int x, int y) {
this.x = x;
this.y = y;
} // Will replace the default constructor.
}
レコードについて話すことがたくさんあります。大きな変化であり、適切な状況で非常に役立つでしょう。ここではすべてを網羅していませんが、これでできることの要点がわかれば幸いです。
6: Pattern Matching (パターンマッチ)
Oracle の冗長性への戦いのもう1つの進展は、Pattern Matching です。Java 14 および Java 15 でプレビューされ、Java 16 で正式にリリースされたパターンマッチングは、instanceof
条件が満たされた後の不要なキャストを取り除く手段です。たとえば、皆がよく知っている状況:
if (o instanceof Car) {
System.out.println(((Car) o).getModel());
}
もちろん、o で Car
のメソッドにアクセスしたい場合はこれが必要です。ですが、2行目で、o が Car
であることに疑問の余地はありません – instanceof
ですでにそれを確認しています。したがって、パターンマッチングでは、小さな変更が行われました:
if (o instanceof Car c) {
System.out.println(c.getModel());
}
これで、オブジェクトのキャストの要点はすべてコンパイラによって実行されます。一見マイナーなようですが、多くのボイラープレートコードをクリーンアップします。これは、オブジェクトがどのタイプであるかが明らかなブランチに入るときの条件付きブランチでも機能します:
if (!(o instance of Car c)) {
System.out.println("This isn't a car at all!");
} else {
System.out.println(c.getModel());
}
instanceof 自体と同じ行でパターンマッチングを使用することもできます:
public boolean isHonda(Object o) {
return o instanceof Car c && c.getModel().equals("Honda");
}
他のいくつかの変更ほど大きくはありませんが、パターンマッチングは、コードのノイズの一般的な原因を効果的にクリーンアップします。
Java は Java 17 で成長を続ける
Java 12 から Java 17 へのアップデートは、もちろんこれだけではありませんが、私が興味深いと思ったのはこれらです。最先端のバージョンの Java で大きなプロジェクトを実行するには、多くの根気が必要です。Java 8 から移行する場合はさらにそうです。
躊躇するとしても、それは理にかなっています。ただし、移行する予定がない場合や、特定のアップグレードが数年後に予定されている場合でも、言語に組み込まれている新機能についていくことは常に良いことです。私の希望は、これらの概念を定着できるように提示することです。そうすれば、読者の誰でも、それらの使用方法について検討を開始できます。
Java 17 は特別です。これは、Java 11 の看板を背負った、次の長期サポートリリースであり、今後数年以内に Java の最も移行されるバージョンになる可能性があります。今準備ができていなくても、学ぶことを開始するのに早すぎることはありません。最終的に Java 17 プロジェクトに取り掛かったとき、あなたはすでに最先端の開発者になっているでしょう!
作者について
Christopher Bielak 氏は、ソフトウェア言語の開発と教育に強い関心を持っている Saggezza (Infostretch 会社) の Java 開発者です。彼は銀行や金融業界で10年の経験があり、最先端のテクノロジーに移行するために派手に主張することがよくあります。