S-JIS[2023-09-23/2024-03-22] 変更履歴

文字列テンプレート(Java21〜22)

Java21〜22の文字列テンプレート(プレビュー版)について。


概要

2023/9/19にリリースされたJava21で、プレビュー版として文字列テンプレートが使えるようになった。

文字列テンプレートは文字列の中に値を埋め込む機能。文字列補間(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";
String s = STR."My name is \{name}";
"My name is hishidama"  
var name = "hishidama";
String s = STR."""
My name is \{name}
""";
"My name is hishidama\n"  
int a = 1;
int b = 2;
String s = STR."\{a} + \{b} = \{a + b}";
"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;

int a = 100;
String s = FMT."a = %04x\{a}";
"a = 0064" FMTは書式を付けられる。
import static java.lang.StringTemplate.RAW;

int a = 1;
int b = 2;
StringTemplate st = RAW."a = \{a}, b = \{b}.";
StringTemplate{ fragments = [ "a = ", ", b = ", "." ], values = [1, 2] } RAWはStringTemplateを返す。
fragmentsとvaluesを交互に結合したら全体の文字列になるっぽい。
int a = 100;
StringTemplate st = RAW."a = %04x\{a}";
String s = FMT.process(st);
"a = 0064"  

テンプレートプロセッサーを自作する例

テンプレートプロセッサーは自作することも出来る。

テンプレートプロセッサーの引数はStringTemplate(RAWで返るもの)で、出力するクラスは任意。
(STRやFMTはStringを出力するようになっている)


STRと同じテンプレートプロセッサーの例

例として、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}";

JDBCのPreparedStatementを生成する例

テンプレートプロセッサーの例として、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が生成できる。


プレビュー機能へ戻る / Java目次へ戻る / 新機能へ戻る / 技術メモへ戻る
メールの送信先:ひしだま