Java 多行字符串字面量——Text Blocks

JDK 15 引入了多行字符串字面量(Text Blocks)特性,不再需要采用字符串拼接的方式来在 Java 代码中定义多行字符串,同时也不需要对字符串内部的双引号做转义,让代码有更好的可读性和可维护性。

使用

定义一个 Text Block 很简单,采用三个双引号 """ (开始分隔符)来开头,后面可跟空白符,然后换行开始输入内容,可以换行或输入直接输入双引号,最后用三个双引号""" (结束分隔符)结束。

Text Block 的内容从开始分隔符后第一个换行符的下一个字符串开始,到结束分隔符前一个字符结束。

var s = """
        <html>
            <body>
                <p>Hello, world!</p>
            </body>
        </html>
        """;

最后将得到,其中用.来代表空格,可以看到保持了多行,并且保持了缩进(注意,最后有一个空行)。

<html>
....<body>
........<p>Hello,.world!</p>
....</body>
</html>

虽然定义形式上和传统的字符串有所区别,但编译后就是 String 类型,所以可以用在所有传统字符串能出现的地方,比如用 + 来连接字符串。

var s = """
        line1
        line2
        line3
        """ + "line4";

缩进

前面提到 text block 会保留缩进,但不是保留所有的空白,不是真正的“字面量”,编译器会在编译时对每行的前后空白符做处理。

处理的主要流程是:

  1. 换行符统一转换为 <LF>,避免因为类似 windows 默认使用 <CR><LF>来换行而导致不同系统下内容不一致的问题
  2. 根据所有非空白行以及最后的行来确定字符串左边(leading)最大的公共空白数量(空格、tab 等都当作一个字符来处理,不因展示上占用的空间不同而不同),然后每行移除左边该数量的空白符。再移除每行尾部所有的空白符。具体见 String.stripIndent 方法。
  3. 前面两个阶段不会对对转义符做处理,在处理完缩进后才使用 String.translateEscape来做处理。
var s = """
            line1
                line2
          line3
            line4""";   // line4 字符前面是一个 tab

实际内容是

...line1
.......line2
.line3
line4

Text block 的最后一行,如果是空行,那么会参数计算左边最大空白数量。所以,如果结束分隔符比较靠左,那么对实际内容的缩进影响可能比较明显。

var s = """
        line1
        line2
        line3
        line4
    """;

实际的内容如下(最后有一个空行):

....line1
....line2
....line3
....line4

转义

Text block 内部可以直接使用 " 而不需要使用 \"来转义,除了直接在结束分隔符(""")前的。

System.out.println("""
     1 "
     2 ""
     3 ""\"
     4 ""\""
     5 ""\"""
     6 ""\"""\"
     7 ""\"""\""
     8 ""\"""\"""
     9 ""\"""\"""\"
    10 ""\"""\"""\""
    11 ""\"""\"""\"""
    12 ""\"""\"""\"""\"
""");

除了已有的 \b\n转义符 外,新增了 \s\<line-terminator> 这两个转义符。\s 表示一个空格(\u0020),\<line-terminator> 表示展示上换行但内容不换行(处理时该转义符被移除)。

参考