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 会保留缩进,但不是保留所有的空白,不是真正的“字面量”,编译器会在编译时对每行的前后空白符做处理。
处理的主要流程是:
- 换行符统一转换为 <LF>,避免因为类似 windows 默认使用<CR><LF>来换行而导致不同系统下内容不一致的问题
- 根据所有非空白行以及最后的行来确定字符串左边(leading)最大的公共空白数量(空格、tab 等都当作一个字符来处理,不因展示上占用的空间不同而不同),然后每行移除左边该数量的空白符。再移除每行尾部所有的空白符。具体见 String.stripIndent 方法。
- 前面两个阶段不会对对转义符做处理,在处理完缩进后才使用 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> 表示展示上换行但内容不换行(处理时该转义符被移除)。