【Modern Java】Java15で正式リリースとなったテキストブロック(JEP 378: Text Blocks)

blog1.mammb.com


JEP 378: Text Blocks

テキストブロックは複数行の文字列リテラルで、ほとんどのエスケープシーケンスが不要になります。

テキストブロックは当初 JDK 12 をターゲットにしていましたがリリースは見送られ、JDK 13 にて JEP 355: Text Blocks (Preview) にてプレビュー機能としてリリースされました。

このリリースのフィードバックとして 2 つの新しいエスケープシーケンスを追加して JDK 14 にて JEP 368: Text Blocks (Second Preview) としてリリースされました。

そしてようやく JDK 15 にて JEP 378: Text Blocks の正式リリースとなりました。

旧来は複数行に渡るテキストリテラルは文字列連結にて以下のように定義していました。

String html = 
        "<html>\n" +
        "    <body>\n" +
        "        <p>Hello, world</p>\n" +
        "    </body>\n" +
        "</html>\n";

テキストブロックを使うことで以下のように視認性に優れた記述が可能となります。

String html = """
      <html>
          <body>
              <p>Hello, world</p>
          </body>
      </html>
      """;


テキストブロックの使い方

テキストブロックには以下のルールがあります。

  • テキストブロックは """ の後に改行(opening delimiters)を加えることで開始して """ (closing delimiters)で終了する
  • テキストブロック内では " はエスケープ無しでそのまま利用できる
    • テキストブロック内に """ を含めたい場合は \""" または \"\"\" のようにエスケープする
  • テキストブロック内の各行の先頭の空白文字は除去される
    • 先頭空白文字数が最も少ない行に合わせて除去される(タブは1文字としてカウントされる)
  • テキストブロック内の各行の末尾の空白文字は除去される
  • 明示的に含めたい場合は \s を使う
  • テキストブロック内の改行は '\n' として扱われる('\r\n' ではない)
    • クロスプラットフォームで同一となるよう正規化が行われる
  • 行末に '\' を付けることで改行がキャンセルできる


" をそのまま使うことができるので SQL クエリなどはとても書きやすくなります。

String query = """
       SELECT "EMP_ID", "LAST_NAME" FROM "EMPLOYEE_TB"
       WHERE "CITY" = 'INDIANAPOLIS'
       ORDER BY "EMP_ID", "LAST_NAME";
       """;

もし最終行の改行が不要な場合は以下のように書くことができます。

String query = """
       SELECT "EMP_ID", "LAST_NAME" FROM "EMPLOYEE_TB"
       WHERE "CITY" = 'INDIANAPOLIS'
       ORDER BY "EMP_ID", "LAST_NAME";""";

利用するメリットは無いですが、テキストブロックで空文字を定義するには以下のようになります。

String empty = """
""";

なお、テキストブロックは先頭の空文字や末尾の空文字除去はコンパイル時に行われます。 実行時には通常の文字列リテラルとして整形後の値が使われます。


テキストブロックのインデントの扱い

空白文字を . で表した場合、

String html = """
..............<html>...
..............    <body>
..............        <p>Hello, world</p>....
..............    </body>.
..............</html>...
..............""";

整形後の文字列リテラルはマージンを | で表すと以下のようになります。

|<html>|
|    <body>|
|        <p>Hello, world</p>|
|    </body>|
|</html>|

このインデントの整形処理は String::stripIndent という新しいインスタンスメソッドで再現することができます。

この整形処理にはエスケープ・シーケンス '\b' (backspace)、'\t' (tab)、'\s' (space) は解釈されないため、明示的に空白文字を制御したい場合には明示的にエスケープ・シーケンスを扱う必要があります。


もし以下のようにテキストブロックを定義した場合、

String html = """
              <html>
                  <body>
                      <p>Hello, world</p>
                  </body>
              </html>
""";

先頭の空白文字の削除は行われずに以下のようになります。

|              <html>
|                  <body>
|                      <p>Hello, world</p>
|                  </body>
|              </html>


エスケープ・シーケンス

文字列の垂直フォーマットには '\n'、'\f'、'\r' を使用することができます。

例えば CRLF('\r\n')改行の文字列がほしい場合には以下のようにします。

String html = """
      <html>\r
          <body>\r
              <p>Hello, world</p>\r
          </body>\r
      </html>\r
      """;

水平フォーマットに '\b' や '\t' を使用することができます。

明示的に改行を除去したい場合は以下のように '\' を使います。

String html = """
      <html>\
          <body>\
              <p>Hello, world</p>\
          </body>\
      </html>\
      """;

これらのエスケープ・シーケンスは整形処理の家庭で対応する Unicode に変換され、新しく追加された 'String::translateEscapes()' インスタンスメソッドで再現することができます。


文字列補間(string interpolation)

テキストブロック自体には文字列補間の機能はありません。

旧来のように文字列連結して加工するか、以下のように String の replace() メソッドを使うことができます。

String code = """
              public void print($type o) {
                  System.out.println(Objects.toString(o));
              }
              """.replace("$type", type);

format() メソッドももちろん使うことができます。

String code = String.format("""
              public void print(%s o) {
                  System.out.println(Objects.toString(o));
              }
              """, type);

通常は、新しく追加された 'String::formatted()' インスタンスメソッドで以下のように置換することができます。

String source = """
                public void print(%s object) {
                    System.out.println(Objects.toString(object));
                }
                """.formatted(type);

formatted() による文字列補間は、テキストブロック中に現れる %%% のようにエスケープできます。

また、%1$s %2$s %3$s のように引数の何番目の文字で置換するかを指定することもできます。このあたりは format() の動きと同じです。