S-JIS[2023-09-23/2024-10-28] 変更履歴
Java21〜22の文字列テンプレート(プレビュー版)について。
|
|
2023/9/19にリリースされたJava21で、プレビュー版として文字列テンプレートが使えるようになった。
ただし、Java23で使えないようになった。(3rdプレビューであるJEP 465は撤回された(Closed/Withdrawn))[2024-10-28]
文字列テンプレートは文字列の中に値を埋め込む機能。文字列補間(string interpolation)とも呼ばれる。
文字列テンプレートは「テンプレートプロセッサー."
テンプレート"
」という形式で表す。
テンプレートはテキストブロック("""
で囲む複数行文字列)でも可。
テンプレート内に埋め込む値は「\{
値}
」で書く。値には変数や計算式が指定可能。
テンプレートプロセッサーは、STRが標準で使える。
var name = "hishidama"; String s = STR."My name is \{name}"; // → "My name is hishidama"
文字列テンプレートはJava21〜22ではプレビュー版の機能なので、この機能を使いたい場合はコンパイル時にjavacコマンドに--enable-previewを付ける必要があり、
また、実行時にjavaコマンドに--enable-previewを付ける必要がある。
JShellで試す場合もjshellコマンドに--enable-previewを付ける。
> javac --enable-preview --release 21 Example.java > java --enable-preview Example
> java --enable-preview --source 21 Example.java
例 | 値が埋め込まれた例 | 備考 |
---|---|---|
var name = "hishidama"; |
"My name is hishidama" |
|
var name = "hishidama"; |
"My name is hishidama\n" |
|
int a = 1; |
"1 + 2 = 3" |
|
String s = STR."Today is
\{LocalDate.now()}" |
"Today is 2023-09-23" |
|
String s = STR."My name is \{zzz}"; |
埋め込まれたシンボル(左記の例では変数zzz)が定義されていない場合、「シンボルが見つかりません」というエラーになる。 | |
import static java.util.FormatProcessor.FMT; |
"a = 0064" |
FMTは書式を付けられる。 |
import static java.lang.StringTemplate.RAW; |
StringTemplate{ fragments = [ "a = ", ", b = ",
"." ], values = [1, 2] } |
RAWはStringTemplateを返す。 fragmentsとvaluesを交互に結合したら全体の文字列になるっぽい。 |
int a = 100; |
"a = 0064" |
テンプレートプロセッサーは自作することも出来る。
テンプレートプロセッサーの引数はStringTemplate(RAWで返るもの)で、出力するクラスは任意。
(STRやFMTはStringを出力するようになっている)
例として、STRと同じテンプレートプロセッサーを作成してみる。
テンプレートプロセッサーはStringTemplateを受け取る。StringTemplateはRAWで生成されるものと同じなので、以下のような内容。
int a = 1; int b = 2; StringTemplate st = RAW"a = \{a}, b = \{b}.";
↓
StringTemplate{ fragments = [ "a = ", ", b = ", "." ], values = [1, 2] }
すなわち、fragmentsとvaluesを交互に結合すれば、出力したい文字列になる。
テンプレートプロセッサーはjava.lang.StringTemplate.Processorインターフェースを実装することで作成する。
StringTemplate.Processorのofメソッドで作成できる。
StringTemplate.Processor<String, ?> MY_STR = StringTemplate.Processor.of((StringTemplate st) -> { var sb = new StringBuilder(); List<String> fragments = st.fragments(); List<Object> values = st.values(); for (int i = 0; i< values.size(); i++) { String fragment = fragments.get(i); sb.append(fragment); Object value = values.get(i); sb.append(value); } sb.append(fragments.getLast()); return sb.toString(); });
int a = 100; String s = MY_STR."a = \{a}";
テンプレートプロセッサーの例として、JEP430のQueryBuilderが面白い。
SQLの文字列をテンプレートとして、JDBCのPreparedStatementを生成するもの。
(PreparedStatementと埋め込む値をペアで毎回生成するので、実用的ではないが)
var DB = new QueryBuilder(connection); int foo = 123; PreparedStatement ps = DB."select * from test where foo = \{foo}"; ResultSet rs = ps.executeQuery();
public record QueryBuilder(Connection connection) implements StringTemplate.Processor<PreparedStatement, SQLException> { @Override public PreparedStatement process(StringTemplate st) throws SQLException { String sql = String.join("?", st.fragments()); PreparedStatement ps = connection.prepareStatement(sql); int i = 1; for (Object value : st.values()) { switch (value) { case Integer i -> ps.setInt(i++, value); case String s -> ps.setString(i++, value); default -> ps.setString(i++, String.valueOf(value)); } } return ps; } }
StringTemplate.Processorの第1型引数は、戻り値の型(ここではPreparedStatement)。
第2型引数はメソッドのthrowsに書かれる例外クラス。
fragmentsの要素の間に値をはさむが、PreparedStatementのSQLの場合はプレースホルダー「?」になる。
そこで、String.join()でdelimiterを「?」にすることでPreparedStatement用のSQLが生成できる。