摘要

JDK13中将加入文本块功能。
本篇文章将有以下内容:

  1. 新版JDK中加入文本块的动机是什么?
  2. 文本块如何使用
  3. 文本块的编译
  4. 文本块的连接

目标

  • 简化跨越多行的字符串,避免对换行等特殊字符进行转义,简化编写Java程序。
  • 增强Java程序中字符串的可读性。

动机

在Java中,通常需要使用String类型表达HTML,XML,SQL或JSON等格式的字符串,在进行字符串赋值时需要进行转义和连接操作,然后才能编译该代码,这种表达方式难以阅读并且难以维护。

无论文本是来自其他编程语言的代码,还是自然语言,在Java程序中表示短文本,中文本和长文本需要有通用的表达方式。

因此,文本块将提高Java程序的可读性和可写性。

HTML example

没有文本块时:

  1. String html = "<html>\n" +
  2. " <body>\n" +
  3. " <p>Hello, world</p>\n" +
  4. " </body>\n" +
  5. "</html>\n";

使用文本块后

  1. String html = """
  2. <html>
  3. <body>
  4. <p>Hello, world</p>
  5. </body>
  6. </html>
  7. """;

SQL example

没有文本块时:

  1. String query = "SELECT `EMP_ID`, `LAST_NAME` FROM `EMPLOYEE_TB`\n" +
  2. "WHERE `CITY` = 'INDIANAPOLIS'\n" +
  3. "ORDER BY `EMP_ID`, `LAST_NAME`;\n";

使用文本块后

  1. String query = """
  2. SELECT `EMP_ID`, `LAST_NAME` FROM `EMPLOYEE_TB`
  3. WHERE `CITY` = 'INDIANAPOLIS'
  4. ORDER BY `EMP_ID`, `LAST_NAME`;
  5. """;

描述

文本块是Java语言中的一种新文字。它可以用来表示任何字符串,并且提供更大的表现力和更少的复杂性。

文本块由零个或多个字符组成,由开始和结束分隔符括起来。

开始分隔符是由三个双引号字符(”””),后面可以跟零个或多个空格,最终以行终止符结束。文本块内容以开始分隔符的行终止符后的第一个字符开始。

结束分隔符也是由三个双引号字符表示,文本块内容以结束分隔符的第一个双引号之前的最后一个字符结束。

文本块中的内容可以直接使用”,\”可以使用,但不是必需的。

文本块中的内容可以直接包括行终止符。允许在文本块中使用\ n,但不是必需的。例如,文本块:

  1. """
  2. line 1
  3. line 2
  4. line 3
  5. """

相当于:

  1. "line 1\nline 2\nline 3\n"

或者一个连接的字符串:

  1. "line 1\n" +
  2. "line 2\n" +
  3. "line 3\n"

如果字符串末尾不需要行终止符,则结束分隔符可以放在最后一行内容上。例如:

  1. """
  2. line 1
  3. line 2
  4. line 3"""

相当于:

  1. "line 1\nline 2\nline 3"

文本块可以表示空字符串,但不建议这样做,因为它需要两行源代码:

  1. String empty = """
  2. """;

以下示例是错误格式的文本块:

  1. String a = """"""; // 开始分隔符后没有行终止符
  2. String b = """ """; // 开始分隔符后没有行终止符
  3. String c = """
  4. "; // 没有结束分隔符
  5. String d = """
  6. abc \ def
  7. """; // 含有未转义的反斜线(请参阅下面的转义处理)

编译处理

文本块是String类型的常量表达式,就像字符串一样。
但是,与字符串不同,Java编译器分为三个步骤来处理文本块的内容:

  1. 内容中的行终止符被转换为LF(\ u000A)。此转换的目的是在跨平台移动Java源代码时遵循最小惊喜原则
  2. 为了匹配Java源代码的缩进,将内容中的多余的空格删除。
  3. 解释内容中的转义字符。这作为最后一步意味着开发人员可以编写转义字符,例如\n,而不会被之前的步骤修改或删除。

最小惊喜原则:总是做最不令人意外的事情

处理后的内容作为常量池中的CONSTANT_String_info条目记录在类文件中,就像字符串一样。

在运行时,文本块将被实例化为String的实例,就像字符串一样。从文本块派生的String实例与从字符串派生的实例是无法区分的。具有相同内容的两个文本块将引用相同的String实例,就像字符串一样。

以下部分将更详细地讨论了编译时的处理。

行终止符

文本块内容中的行终止符由Java编译器把CR(\ u000D)和CRLF(\ u000D \ u000A)标准化为LF(\ u000A)。这样确保了文本块内容在不同平台上是等效的。

多余的空格

下面这段代码中,我们用.来表示我们代码中的的空格,而这些位置的空格就是多余的。

  1. String html = """
  2. ..............<html>
  3. .............. <body>
  4. .............. <p>Hello, world</p>
  5. .............. </body>
  6. ..............</html>
  7. ..............""";

多余的空格还会出现在每一行的结尾,特别是当你从其他地方复制过来时,更容易出现这种情况,比如下面的代码:

  1. String html = """
  2. ..............<html>...
  3. .............. <body>
  4. .............. <p>Hello, world</p>....
  5. .............. </body>.
  6. ..............</html>...
  7. ..............""";

这些多余的空格对于程序员来说是看不到的,但是他又是实际存在的,所以如果编译器不做处理,可能会导致程序员看到的两个文本块内容是一样的,但是这两个文本块却因为存在这种多余的空格而导致差异,比如哈希值不相等。

所以编译器在编译时会删除掉这些多余的空格。

转义字符

允许开发人员使用\ n,\ f和\ r来进行字符串的垂直格式化,使用\ b和\ t进行水平格式化。比如下面的代码是合法的:

  1. String html = """
  2. <html>\r
  3. <body>\r
  4. <p>Hello, world</p>\r
  5. </body>\r
  6. </html>\r
  7. """;

请注意,在文本块内自由使用”是合法的,即使在开始或结束分隔符旁边也是如此。例如:

  1. String story = """
  2. "When I use a word," Humpty Dumpty said,
  3. in rather a scornful tone, "it means just what I
  4. choose it to mean - neither more nor less."
  5. "The question is," said Alice, "whether you
  6. can make words mean so many different things."
  7. "The question is," said Humpty Dumpty,
  8. "which is to be master - that's all."""";

但是,三个”字符的序列需要进行转义至少一个”以避免模仿结束分隔符:

  1. String code =
  2. """
  3. String text = \"""
  4. A text block inside a text block
  5. \""";
  6. """;

文本块连接

可以在任何可以使用字符串的地方使用文本块。例如,文本块和字符串可以相互连接:

  1. String code = "public void print(Object o) {" +
  2. """
  3. System.out.println(Objects.toString(o));
  4. }
  5. """;

但是,涉及文本块的连接可能变得相当笨重。以下面文本块为基础:

  1. String code = """
  2. public void print(Object o) {
  3. System.out.println(Objects.toString(o));
  4. }
  5. """;

假设我们想把上面的Object改为来至某一变量,我们可能会这么写:

  1. String code = """
  2. public void print(""" + type + """
  3. o) {
  4. System.out.println(Objects.toString(o));
  5. }
  6. """;

可以发现这种写法可读性是非常差的,更简洁的替代方法是使用String :: replace或String :: format,比如:

  1. String code = """
  2. public void print($type o) {
  3. System.out.println(Objects.toString(o));
  4. }
  5. """.replace("$type", type);
  1. String code = String.format("""
  2. public void print(%s o) {
  3. System.out.println(Objects.toString(o));
  4. }
  5. """, type);

另一个方法是使用String :: formatted,这是一个新方法,比如:

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

参考:https://openjdk.java.net/jeps/355

总结

本文写在JDK13还未正式发布之前,也是希望能让大家先睹为快,同时希望JDK13能尽快发布,让我们Javer能用上更方便的语言。
如果觉得这篇文章能让你学到知识,能否帮忙转发,将知识分享出去。
如果想第一时间学习更多的精彩的内容,请关注微信公众号:1点25
image.png