本文由 yanglbme 原创,首发于公众号“ Doocs开源社区 ”,禁止未授权转载。
“你继续半年一更,我继续用 Java 7/8”。
这可能是很多朋友目前的真实情况。说实话,Java “ 每半年发布一个新版本 ”的节奏确实快了点。但不管怎样,新技术、新特性还是值得我们学一学。
在 Java 13 中,有了文本块,我们可以轻松地使用多行字符串。我们不再需要对字符串中的特殊字符进行转义,也不必对跨越多行的字符串使用 +
串联运算符,它极大地提高代码的可读性。在这篇文章中,我将介绍关于 Java 13 文本块的功能以及使用场景,一起来看看吧。
String 数据类型可能是 Java 开发人员最常用的类型之一。它可以以任何语言存储从几个字符到多行的所有内容。不过,这种灵活性也导致某些 String 值难以阅读或修改。例如,那些带有引号,转义字符或跨越多行的字符串。
文本块是 Java 13 的一项 预览功能 ,我们可以使用文本块轻松定义多行字符串而无须添加串联运算符和转义字符。此外,我们还可以控制如何格式化字符串值。
让我们看看以下 HTML 代码段:
String html = """ <HTML> <BODY> <H1>"Java 13 is here!"</H1> </BODY> </HTML>"""; 复制代码
注意,三个双引号 """
分别定义了块的开始和结尾。看一下在 Java 13 以前是如何定义的:
String html = "<HTML>/n/t<BODY>/n/t/t<H1>/"Java 13 is here!/"</H1> /n/t</BODY>/n</HTML>/n"; 复制代码
或者,更加典型的是:
String html = "<HTML>" + "/n/t" + "<BODY>" + "/n/t/t" + "<H1>/"Java 13 is here!/"</H1>" + "/n/t" + "</BODY>" + "/n" + "</HTML>"; 复制代码
上面的这两种写法,在可读性上远远不如不如文本块。
就像前面提到的,Java 13 使用三个双引号 """
作为开始和结束定界符来定义文本块。 开始定界符 后面可以 跟零个或者多个空格 和 一个行终止符 ; 结束符没有类似的规则 。
这样的语法规则,导致以下示例其实是 无效的文本块 , 因为它们在开头定界符之后并没有包含一个行终止符 :
String multilineValue1 = """ """; String multilineValue2 = """"""; 复制代码
文本块在 Java 13 中作为预览特性发布,但预览特性并不意味着它是不完整或半生不熟的功能。从本质上讲,这意味着即使开发人员可以使用此功能,它也可能在将来的 Java 版本中更改。
这其实是有原因的。通过“每六个月发布一个新版本”的节奏,开发人员可以很快使用到新的语言功能。但是,在向 Java 永久添加语言功能之前,Java 团队会评估开发人员对此的评价。根据反馈的不同,可以在将预览功能添加到 Java SE 之前对其进行完善,也可以将其完全删除。
要体验预览语言功能,我们需要使用编译和运行时手动启用它们。这样可以确保我们不会意外使用预览功能。要使用文本块 编译 源文件,请使用 --enable-preview
和 -release 13
选项。以下示例演示了如何使用命令行来 编译 源文件,例如对于 Java13.java
文件:
javac --enable-preview --release 13 Java13.java 复制代码
为了加强预览功能可能会发生的变化,当我们执行前面的命令时,将会得到如下所示的编译器警告。
Note: Java13.java uses preview language features. Note: Recompile with Xlint:preview for details. 复制代码
要执行类 Java13,我们需要使用 --enable-preview
选项:
java --enable-preview Java13 复制代码
接下来,我们来看一下文本块的实现。
传统的字符串和文本块都被编译为相同的类型: String
。字节码类文件无法区分字符串值是从传统字符串还是文本块派生而来。这意味着文本块值存储在字符串池中。
考虑以下代码,你觉得变量 traditonalString
和 textBlockString
引用相同的 String 实例吗?
String traditionalString = "Java"; String textBlockString = """ Java"""; System.out.println(traditionalString == textBlockString); 复制代码
答案是肯定的,因为它们的内容是完全相同的,所以上面的代码将会输出 true
。
在文章的开头,我讨论了使用传统的 String 处理多行字符串的繁琐,接下来,我们就来看看文本块在 Java 13 中如何提供便利?
开发人员经常使用多行字符串值,例如 JSON,HTML,XML 或正则表达式(regex)数据。通过文本块,使用多行 JSON 值的方法变得很简单。
String json = """ { "name": "web", "version": "1.0.0", "dependencies": "AppA" } """; 复制代码
没有了转义字符和连接运算符,代码在视觉上显得很直观,因此,我们可以轻松编辑修改 JSON 值。不信?对比一下使用传统的 String 来定义 JSON 值:
String json = "{" + "/"name/": /"web/"," + "/"version/": /"1.0.0/"," + "/"dependencies/": /"AppA/" + "}"; 复制代码
有时候,我们需要将 SQL 查询语句存储为字符串。假如我们使用 String 变量存储了一个多行 SQL 查询语句,如下所示(对于 Java 12 或更早版本):
String query = "SELECT name, age" + "FROM EMP" + "WHERE name = /'John/' + "AND age > 20"; 复制代码
是不是觉得好像没毛病?其实是 有问题 的,它是一个无效的 SQL 查询语句。由于每行末尾都少了空格,这个查询语句会被解释为以下内容:
SELECT name, ageFROM EMPWHERE name = 'John'AND age > 20 复制代码
要是使用文本块,就可以避免出现类似问题:
String query = """ SELECT name, age FROM EMP WHERE name = 'John' AND age > 20 """; 复制代码
正则表达式通常会包含许多特殊字符。在 Java 13 之前,我们将一个正则表达式写为 String 时,常常需要转义一些特殊字符,这使得我们难以编写、阅读或理解这些表达式。
这是一个在文本块之前存储正则表达式 [0-9]{3}/-[0-9]{4}
的示例:
String java12OrBeforeRegex = "[0-9]{3}//t-[0-9]{4}"; 复制代码
有了文本块,我们可以定义出相同的正则表达式,如下所示:
String java13Regex = """ [0-9]{3}/t-[0-9]{4}"""; 复制代码
我们可以将各种转义字符添加到文本块,就像将它们添加到 String 中一样。例如,可以通过将值放在多行上或使用转义字符(例如 /n
)来在文本块中包含换行符。在以下代码中, I'm
和 happy
将会分别在两个行上。
String html = """ <HTML> <BODY> <H1>I'm /nhappy</H1> </BODY> </HTML>"""; 复制代码
一个关键问题是 如何处理文本块中附带的空白符 。事实上,它由编译器很好地处理。在文本块中,任何行上 最左边的非空白字符 或 最左边的分隔符 定义了字符串开始的位置。
在下图中,我们可以看到 getHTML()
方法返回了一个 String 类型的值,String 值的最左非空白字符以 <
( <HTML>
中的 <
)开头, 该字符与结束定界符对齐 。
上面图中的这段代码,其实返回了下图所示的字符串(第一行 <HTML>
和最后一行 </HTML>
都不包含任何前导空格):
若要将字符串开头的空白字符标记为必需的(即:保留 <HTML>
左侧的空白符),我们需要向左移动分隔符或者任何非空白字符。
比如,我们将闭合定界符 """
向左移动 8 个空格:
修改后的代码将会返回下图所示的 String 值,可以看到每行都添加了 8 个前导空格,外加用户额外缩进的空格。
默认情况下,文本块会删除每行末尾的空格。如果需要添加它们,可以使用八进制转义字符 /040
(在 ASCII 中,空格为 32)来强制包含它们。
以下是一个示例,该示例在文本块第二行的末尾添加了一个空格:
String campaign = """ Don't leave home without - money &/040 carry bag. Reduce | Reuse """; 复制代码
注意,如果基本空格包含制表符 /t
,则不会将其扩展,而是算作单个空格。
文本块可以与传统的字符串值通过 +
连接,以下是一个例子:
String concatenate() { return """ Items to avoid - Single Use Plastics """ + "Let's pledge to find alternatives"; } 复制代码
之所以提供这种方式,是因为我们有时候需要在字符串中插入变量的值:
String concatenate(Object obj) { return """ Items to avoid - Single Use """ + obj + """ Let's pledge to find alternatives"""; } 复制代码
文本块可以当作字符串来使用,好处就是我们可以在文本块中使用 String 的方法。例如,我们可以使用 String.replace()
方法而无需做任何特殊处理:
String concatenateReplace(Object obj) { return """ Items to avoid - Single Use $type Let's pledge to find alternatives""".replace("$type", obj.toString()); } 复制代码
同样,我们可以使用 format() 方法或者 String 的任何其他方法。