Java 六个月的版本迭代周期让我们突然间有些喘不过气来,又 Java 11 才会是一个长期支持版本,如果可能的话我们将会是从 Java 8 直接往 Java 11 跳去。在 Java 8 大行其道,裹足不前的当下,谈论着未来 Java 11 不确定的新特性,有一种看着别人家碗里的不过瘾,还要看着别人家锅里的感觉。
本篇原本只是为了对 Java 11 潜在的原始字符串字面量(Raw String Literals)的哨探,同时考虑到 Java 10 仅有的一个语言层面的新特性 var
局部变量类型推断,所以顺带介绍一下。
我这儿把它称之为 var 局部变量类型推断,说明了var
类型推断并不适用于类或实例的变量,或方法的参数。而且 var
也并未上升为一个 Java 的关键字,我们仍然可以用 var
作为变量或方法名。
对局部变量用 var
进行类型推断的代码示例变化如下:
1 2 3 4 5 6 7 |
List<String> list = new ArrayList<>(); //Java 10 以前 var list = new ArrayList<String>(); //Java 10 开始 int age = 100; //Java 10 以前 var age = 100; //Java 10 开始,推断为 int 类型 var stream = blocks.stream(); //都不需要引入 java.util.Stream 类 |
var
学了 Scala
的语法,再重复一遍,var
只能用于局部变量,这有一个好处就是局部变量的跨越性较小,不至于 var
声明的变量阅读时只有编译器知道它的确切类型。var
类型推断给代码书写上的变化就是对局部变量的任何类型声明都可以替代为 var
。
看到前面的 var
,仿佛觉得它摇身一变就成了 Java 的关键字,其实不然,var
仍然可用作普通的变量或方法名,下面的代码是可以被正常编译执行的
1 2 3 4 5 6 7 8 |
public static void main(String[] args) { int var = 100; var(var); } private static void var(int var) { System.out.println(var); } |
简单见识过 Java 10 的 var
后,我们再推进到尚处于愿景中的 Java 11 特性: 原始字符串字面量(Raw String Literals, 含多行字符串,避免转议,heredoc,但不包含字符串插值)。JEP 326: Raw String Literals, 仍处于候选状态,相应 Issue 是 JDK-8198986: 3.10.7: Raw string literals, 活跃当中,优先级是 P3。不管是否能在 Java 11 还是后续的版本中加入,先了解一下这个 JEP 是干什么的。
var
仅让我们免去了书写变量的类型,以不变应万变,仅乎改进体验收效不甚明显,那么 JEP(JDK Enhancement Proposal) 326 将会带我们带来更大的快感。
回顾一下我当年对 Java 的多行字符串是多么的渴望,尝试过用 Java 的编译期 AnnotationProcessor 的方式: Java 的多行字符串 Here Document 的实现。后来完全对此失望了,Java 对某些众多语言共有便利特性的追逐总是慢了十拍,要多行字符串的时候就直接求助于 Scala 或 Groovy 语言,只是让一个项目变得更复杂了些。
说几个切实的例子,由于 Java 没有多行字符串的表示法,我们在代码中写一段 SQL 语句,JSON 字符串,或 XML 字符串,我们不得不用加号换行连接,还得对其中的双引号进行转议,对正则表达式中的关键字也必须进行双重转议。
1 2 3 4 5 6 7 8 9 |
String sql = "select id, name \n " + " from user \n" + " where id=? \n" + " and deleteFlag = 0"; String json = "{\n" + "\"id\": 123,\n" + "\name\": \"Yanbin\"\n" + "}"; |
上面只是一个简单示例,实际项目中的 SQL 或 JSON 会比这个更加庞大,导致大量的连接与转议,书写起来特不方便。也许会说可以在外部先写好,用工具一次性转议即可,但代码要进行修改时又苦了。所以有时候不得不把大段的类似的格式化文本外部化到文件中,只在运行时载入。
如果候选中的 JEP 326 能够成行的话,上面的代码只要写成
1 2 3 4 5 6 7 8 9 |
String sql = `select id, name from user where id=? and deleteFlag = 0`; String json = `{ "id": 123, "name": "Yanbin" }`; |
如果打印它们的话可保持一样的格式输出,是不是有一种所见即所得的即视感?该 JEP 326: Raw String Literals,描述的是用斜撇号(backtick) 来作为边界符,其中的字符无须进行转议,如果中间有 \n
或 \u2022
, 它们都将以字面量的形式输出,所以才叫做 Raw String Literals
。斜撇号的表示法是参考了 Go, JavaScript 的做法。Scala 或 Groovy 是用三个双引号 """
。
如果字符串字面量中含有斜撇号的话,边界用双重斜撇号,如
1 2 3 4 5 |
String query = `` SELECT `EMP_ID`, `LAST_NAME` FROM `EMPLOYEE_TB` WHERE `CITY` = ‘INDIANAPOLIS' ORDER BY `EMP_ID`, `LAST_NAME`; ``; |
下面是几个 JEP 326 中提到的问题
多行字符串时边界的管理
如果对上面的 json
变量输出的控制台,将会是下面的样子 (点号代表空格)
{
................."id": 1234
................."name": "Yanbin"
.................}
如果需要我们期望的漂亮格式,代码声明就得改成
1 2 3 4 |
String json = `{ "id": 123, "name": "Yanbin" }`; |
也不赖,边界的管理也就发生在第一行和最末一行。该 JEP 326 中未描述边界如何控制,可看看 Scala 的做法
1 2 3 4 |
val json = """|{ | "id": 123, | "name": "Yanbin" |}""".stripMargin |
有了那道杠杠,总能把格式控制的漂漂亮亮。
边界符的选择
JEP 326 中用的是一个或两个斜撇号,正式加入该特性后,都说不定。边界符的选择需避免二义性,又要在实际字符串中出现频度尽可能少的,可借鉴比较通行的用法,如
"""..."""
: Groovy, Kotlin, Python, Scala, Swift
: Go, JavaScript
...
@"..."
: C#
R"..."
: Groovy (old style)
R"xxx(...)xxx"
: C/C++
%(...)
: Ruby
qq{...}
: Perl
关于字符串插值(interpolation)
该 JEP 并不打算实现这一功能,或许在将来。因此基本第一个版本别希望下如下字符串中的变量引用与方法调会被解析
1 |
`Hello $name, number: ${count.get()}` |
Heredoc 的支持
Here document 是 Linux Shell 和 Perl 爱用的多行字符串表示法,以任意配对的首尾标记来框住多行字符串,例如:
1 2 3 4 5 6 7 |
System.out.println(<<HTML); <html> <body> <p>Hello World.</p> </body> </html> HTML |
由于该 JEP 仍处在候选状态,开发过程中,所以下载不到一个含该特性的 JDK 11 进行抢先体验,最终 JDK 11 的发布将在九月份,不知三个月后能否事与愿为。已包含在 JDK 11 的 JEP 可由该链接 http://openjdk.java.net/projects/jdk/11/ 查看到,从目前迹象来看 JEP 326 进入 JDK 11 的希望很渺茫。
关于对 Java 语言的期待,或者说自以为的 Java 语言的几个缺陷 的抱怨可参考下方 相关阅读
中的几篇日志。