java 11 新特性

前言

Java 11 已于 2018 年 9 月 25 日正式发布,为了加快的版本迭代、跟进社区反馈,Java 的版本发布周期调整为每六个月一次——即每半年发布一个大版本,每个季度发布一个中间特性版本,并且做出不会跳票的承诺。通过这样的方式,Java 开发团队能够将一些重要特性尽早的合并到 Java Release 版本中,以便快速得到开发者的反馈,避免出现类似 Java 9 发布时的两次延期的情况。

按照官方介绍,新的版本发布周期将会严格按照时间节点,于每年的 3 月和 9 月发布,Java 11 发布的时间节点也正好处于 Java 8 免费更新到期的前夕。与 Java 9 和 Java 10 这两个被称为"功能性的版本"不同,Java 11 仅将提供长期支持服务(LTS, Long-Term-Support),还将作为 Java 平台的默认支持版本,并且会提供技术支持直至 2023 年 9 月,对应的补丁和安全警告等支持将持续至 2026 年。

本文主要针对 Java 11 中的新特性展开介绍,快速了解 Java 11 带来的变化。

基于嵌套的访问控制

嵌套访问控制是一种控制上下文访问的策略,允许逻辑上属于同一代码实体,但被编译之后分为多个分散的 class 文件的类,无需编译器额外的创建可扩展的桥接访问方法,即可访问彼此的私有成员,并且这种改进是在 Java 字节码级别的。

在 Java 11 之前的版本中,编译之后的 class 文件中通过 InnerClasses 和 Enclosing Method 两种属性来帮助编译器确认源码的嵌套关系,每一个嵌套的类会编译到自己所在的 class 文件中,不同类的文件通过上面介绍的两种属性的来相互连接。这两种属性对于编译器确定相互之间的嵌套关系已经足够了,但是并不适用于访问控制。

Java 11 中引入了两个新的属性:一个叫做 NestMembers 的属性,用于标识其它已知的静态 nest 成员;另外一个是每个 nest 成员都包含的 NestHost 属性,用于标识出它的 nest 宿主类。

标准 HTTP Client 升级

在Java 9 中开始引入了一个用于处理HTTP请求的HTTP Client API, 该 API 支持同步和异步,而在 Java 11 中已经为正式可用状态,可以在 java.net 包中找到这个 API,看一下这个API的用法:

1
2
3
4
5
6
7
8
9
10
11
12
String uri = "http://www.oracle.com/";
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder()
.uri(URI.create(uri))
.GET()
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString()); // 同步请求
System.out.println(response.body());

client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenAccept(System.out::println); // 异步请求

Http请求的默认方式为GET。我们终于可以摆脱老旧的HttpURLConnection了😄,但是是否可以真正地取代Apache的HttpClient工具包还待进一步验证。

字符串API加强

Java 11 对原来的字符串API进行了增强,下面的代码演示了其中部分增强的API用法:

1
2
3
4
5
6
7
String s = "Javastack ";
s = s.strip(); // 去除字符串首位的空格
s = s.stripLeading(); // 去除字符串开头的空格
s = s.stripTrailing(); // 去除字符串末尾的空格
s = s.repeat(3); // 字符串重复拼接
s.isBlank(); // 判断字符串是否为空或者只包含空格
int c = s.lines().count(); // 计算字符串中包含的行数(以换行符分隔)

Java 启动器增强

在之前的版本,我们运行一个Java主类需要两步,即先编译然后再执行。而在Java 11 中,通过一个Java命令就可以搞定了:

1
$ java Main.java

用于 Lambda 参数的局部变量语法

本地变量类型var是java 10提出的新概念,它可以从上下文中推断出本地变量的类型,从而提高代码可读性。

1
2
3
4
5
6
7
8
public class Main {
public static void main(String[] args) throws Exception {
URL url = new URL("http://www.oracle.com/");
URLConnection conn = url.openConnection();
Reader reader = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
}
}

使用var声明后,代码的写法可以变成如下的形式:

1
2
3
4
5
6
7
8
public class Main {
public static void main(String[] args) throws Exception {
var url = new URL("http://www.oracle.com/");
var conn = url.openConnection();
var reader = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
}
}

在上面的代码中,我们使用var代替了URL、URLConnection、Reader,提高了代码的可读性,也方便了开发。 但是在Java 10中,var变量不能在lambda表达式中声明,在Java 11中,解决了这个问题。现在,我们可以在lambda表达式中使用var,例如:

1
2
Map<String, Object> map 
= new TreeMap<String, Object>((var a, var b) -> a.compareTo(b));

ZGC

ZGC 即 Z Garbage Collector(垃圾收集器或垃圾回收器),这应该是 Java 11 中最为瞩目的特性,没有之一。ZGC 是一个可伸缩的、低延迟的垃圾收集器,主要为了满足如下目标进行设计:

  • GC 停顿时间不超过 10ms
  • 即能处理几百 MB 的小堆,也能处理几个 TB 的大堆
  • 应用吞吐能力不会下降超过 15%(与 G1 回收算法相比)
  • 方便在此基础上引入新的 GC 特性和利用 colord
  • 为 Load barriers 优化奠定基础

不过目前 ZGC 还处于实验阶段,目前只在 Linux/x64 上可用,如果有足够的需求,将来可能会增加对其他平台的支持。

作为实验性功能的 ZGC 将不会出现在 JDK 构建中,除非在编译时使用 configure 参数:–with-jvm-features=zgc 显式启用。编译完成之后,需要配置以下 JVM 参数,才能使用 ZGC,具体启动 ZGC 参数如下:

1
-XX:+ UnlockExperimentalVMOptions -XX:+ UseZGC -Xmx10g

ZGC 是一个并发收集器,必须要设置一个最大堆的大小,应用需要多大的堆,主要有下面几个考量:

  • 对象的分配速率,要保证在 GC 的时候,堆中有足够的内存分配新对象。
  • 一般来说,给 ZGC 的内存越多越好,但是也不能浪费内存,所以要找到一个平衡。

所有的Java 11 新特性参考链接