追加されたインターフェースとそのメソッド
リスト1が追加された各インターフェースの概要コードです。主に、first/lastを名前の一部に持つメソッドが追加されています。各実装クラスにおいて同じことをしようとするとインデックス値を使って処理を行わなければならない場合がありましたが、今後はより直感的に操作できると思います。
また、reversedメソッドが追加されたことで、順番を逆から参照することも容易になりました。ただし、reversedメソッドで得られる新たなコレクションは、元のコレクションインスタンスの参照となります。保持しているデータ自体は同じなので、通常の場合、変更や更新などを行うとそれらも反映されます。その点には注意してください(ただし、定義しているのはインターフェースであるため、すべての実装において保証されているものではありません)。
public interface SequencedCollection<E> extends Collection<E> { SequencedCollection<E> reversed(); void addFirst(E e); void addLast(E e); E getFirst(); E getLast(); E removeFirst(); E removeLast(); } public interface SequencedSet<E> extends SequencedCollection<E>, Set<E> { SequencedSet<E> reversed(); } public interface SequencedMap<K, V> extends Map<K, V> { SequencedMap<K, V> reversed(); Map.Entry<K,V> firstEntry(); Map.Entry<K,V> lastEntry(); Map.Entry<K,V> pollFirstEntry(); Map.Entry<K,V> pollLastEntry(); V putFirst(K k, V v); V putLast(K k, V v); SequencedSet<K> sequencedKeySet(); SequencedCollection<V> sequencedValues(); SequencedSet<Map.Entry<K, V>> sequencedEntrySet(); }
レコードパターン(JEP440)とSwitchでのパターンマッチ(JEP441)
Switchでのパターンマッチはこちらでも紹介しているJava17のPreview版からも続く改善です。それらが、レコードパターンとも関連して使えるようになり、正式版としてリリースされました。この変更ではリスト2のような書き方が可能になっています。
レコードパターンの利用例(src/main/java/jp/enbind/jep440/Main.javaの抜粋)] record Point(int x,int y){}; void sample1(){ var p1 = new Point(100,120); // (1) Java16まで if(p1 instanceof Point p){ System.out.println("x:" + p.x + ",y:" + p.y); } // (2) Java21から if(p1 instanceof Point(int x,int y)){ System.out.println("x:" + x + ",y:" + y); } }
(1)の書き方はJava16から可能でした。そして、Java21からは(2)のようにrecord定義のなかの変数にまで直接アクセスできる宣言が可能になっています。また、record定義の変数自体が他のrecord定義であるようなネストされたrecord定義であってもリスト3のようにアクセス可能です。
record Line(Point start,Point end){}; void sample2(){ var p1 = new Point(0,0); var p2 = new Point(100,100); var line = new Line(p1,p2); // (1) ネストされた変数にもアクセス可能 if(line instanceof Line(Point(int x,int y),Point p)){ System.out.println("line x1:" + x + ",y1:" + y + "->" + "x2:" + p.x + ",y2:" + p.y); } // (2) varで置き換えることもできる if(line instanceof Line(var v1,var v2)){ System.out.println("line x1:" + v1.x + ",y1:" + v1.y + "->" + "x2:" + v2.x + ",y2:" + v2.y); } }
ネストされたrecord定義は(1)のように記述することができます。そして、あえてネストさせない記述も混ぜることができるので、その後に利用する実装に応じて使い分けることが可能です。
このようにインスタンスを展開していく必要がなくなるので、よりコードが読みやすくなると思います。ただし、あまりにも深いネスト表現を使うと逆に可視性が下がるので、ご注意ください。
また、(2)のようにvar記述を使って型推論を利用した宣言も可能です。ただし、使わない変数についての定義をしなければならないことに多少の面倒さがまだ残ります。ここの改善は、無名パターンと無名変数(JEP443)を利用して可能です。これについては、改めて後述します。
そして、このレコードパターンはSwitchでも利用できます。
void sample1Switch(Object obj){ switch (obj){ case Point(int x,int y) -> { // (処理) } case Line(Point(int x,int y),Point e) -> { // (処理) } default -> { // (処理) } } }
このようにrecord定義とswitchを組み合わせて使うことでさまざまなパターン一致の表現がしやすくなりました。
文字列テンプレート(JEP430)-Preview
文字列内に変数の値を挿入したいケースは多々あります。ここまでの例でもリスト5のように文字列と変数を連結して使っていました。
System.out.println("line x1:" + x + ",y1:" + y + "->" + "x2:" + p.x + ",y2:" + p.y);
このような記述方法は直感的に読みにくいと思います。これまでもFormatterクラスを使うことで解決することはできました。しかし、他の言語であればもっと分かりやすい表現ができるようになっています。例えば、JavaScriptではリスト6のように書き換えることができます。
const str = `line x1:${x},y1:${y} -> x2:${p.x},y2:${p.y}`;
このような書き方ができるようにするための変更が、この文字列テンプレートです。
import static java.lang.StringTemplate.STR; // : (省略) int x = 0; int y = 100; // (1) 基本的な例 String line = STR."x is \{x}, y is \{y}"; // x is 0, y is 100 var map = new HashMap<String,Integer>(); map.put("grape",5); // (2) 応用例 String data = STR.""" りんご : \{map.get("apple").intValue()} ばなな : \{map.get("banana").intValue()} いちご : \{map.containsKey("strawberry")? map.get("strawberry").intValue() : "-"} // (3) """; // りんご : 3 // ばなな : 6 // いちご : -
ここで注目すべきはSTRです。この部分が文字列の変換を行う実装になっています。そして、(1)のようにバックスラッシュ(\)と{}を使って変数の値を差し込みます。また、(2)のように変数のインスタンスやメソッドも利用可能であり、(3)のようにすればnullの場合にも対応できます。
そして、STRの部分の実装は変更が可能です。例えば、java.util.Formatterと同じような記述ができるテンプレート実装もあり、その利用例がリスト8です。
import static java.util.FormatProcessor.FMT; // (省略) var map = new HashMap<String,Double>(); String data = FMT.""" -- 名称 -- : - 単価 - りんご : %7.2f\{map.get("apple").doubleValue()} ばなな : %7.2f\{map.get("banana").doubleValue()} いちご : %7.2f{map.containsKey("strawberry")? map.get("strawberry").doubleValue() : 0.0} // (1) """"; // -- 名称 -- : - 単価 - // りんご : 180.50 // ばなな : 198.20 // いちご : 0.00
このフォーマットではバックスラッシュの前にString.formatやjava.util.Formatterで使えるフォーマット指定を行います。STRと同じようにnullの場合のような記述も可能ですが、その値に対するフォーマット処理がされるので(1)のようにフォーマットで対応できる型にしないといけません。
また、このテンプレート形式は自分で自作することも可能です。そのため、例えば、SQLを構築するためのテンプレートやHTMLを構築するためのテンプレートを作ることも可能です。自作テンプレートの実装例はサンプルコード(src/main/java/jp/enbind/jep430/Main.java)にあるので、詳しく知りたい方はそちらを参照してください。