背景
Java 11 已于 2018 年 9 月 25 日正式发布,Java 开发团队为了加快的版本迭代、跟进社区反馈,Java 的版本发布周期调整为每六个月一次——即每半年发布一个大版本,每个季度发布一个中间特性版本,并且做出不会跳票的承诺。通过这样的方式,Java 开发团队能够将一些重要特性尽早的合并到 Java Release 版本中,以便快速得到开发者的反馈,避免出现类似 Java 9 发布时的两次延期的情况。
本文主要针对 Java 9 - Java 11 中的新特性展开介绍,让您快速了解 Java 9 - Java 11 带来的变化。
特性介绍
按照官方介绍,新的版本发布周期将会严格按照时间节点,于每年的 3 月和 9 月发布,Java 11 发布的时间节点也正好处于 Java 8 免费更新到期的前夕。与 Java 9 和 Java 10 这两个被称为 “功能性的版本” 不同,Java 11 仅将提供长期支持服务(LTS, Long-Term-Support),还将作为 Java 平台的默认支持版本,并且会提供技术支持直至 2023 年 9 月,对应的补丁和安全警告等支持将持续至 2026 年。
REPL(JShell)@since 9
REPL(Read Eval Print Loop)意为交互式的编程环境。JShell 是 Java 9 新增的一个交互式的编程环境工具。它允许你无需使用类或者方法包装来执行 Java 语句。它与 Python 的解释器类似,可以直接 输入表达式并查看其执行结果。JShell 它提供了一个交互式 shell,用于快速原型、调试、学习 Java 及 Java API,所有这些都不需要 public static void main 方法,也不需要在执行之前编译代码。
1、执行 JSHELL
1 | $ jshell |
2、查看 JShell 命令:输入 /help 可以查看 JShell 相关的命令
1 | jshell> /help |
3、JShell 执行使用
1 | // JShell 执行计算 |
4、退出 JShell:输入 /exit 命令退出 jshell
1 | /exit |
局部变量类型推断 @since 10
从 Java 10 开始,便引入了局部变量类型推断这一关键特性。类型推断允许使用关键字 var 作为局部变量的类型而不是实际类型,编译器根据分配给变量的值推断出类型。这一改进简化了代码编写、节省了开发者的工作时间,因为不再需要显式声明局部变量的类型,而是可以使用关键字 var,且不会使源代码过于复杂。var 是 Java10 中新增的局部类型变量推断。它会根据后面的值来推断变量的类型,所以 var 必须要初始化。var 其实就是 Java 10 增加的一种语法糖而已,在编译期间会自动推断实际类型,其编译后的字节码和实际类型一致。
但是在 Java 10 中,还有下面几个限制:
- 只能用于局部变量上
- 声明时必须初始化
- 不能用作方法参数
- 不能在 Lambda 表达式中使用
1 | // var 定义局部变量[等同于 int a = 1;] |
用于 Lambda 参数的局部变量语法 @since 11
Java 11 与 Java 10 的不同之处在于允许开发者在 Lambda 表达式中使用 var 进行参数声明。乍一看,这一举措似乎有点多余,因为在写代码过程中可以省略 Lambda 参数的类型,并通过类型推断确定它们。但是,添加上类型定义同时使用 @Nonnull 和 @Nullable 等类型注释还是很有用的,既能保持与局部变量的一致写法,也不丢失代码简洁。
1 | @Nonnull var x = new Foo(); |
局部变量类型推断优缺点
(1)优点:简化代码
1 | CopyOnWriteArrayList list1 = new CopyOnWriteArrayList(); |
从以上代码可以看出,很长的定义类型会显得代码很冗长,使用 var 大大简化了代码编写,同时类型统一显得代码很对齐。
(2)缺点:掩盖类型
1 | var token = new JsonParserDelegate(parser).currentToken(); |
看以上代码,不进去看返回结果类型,谁知道返回的类型是什么?所以这种情况最好别使用 var,而使用具体的抽象类、接口或者实例类型。
私有接口方法 @since 9
在 Java 8 之前,接口可以有常量变量和抽象方法。我们不能在接口中提供方法实现。如果我们要提供抽象方法和非抽象方法(方法与实现)的组合,那么我们就得使用抽象类。
在 Java 8 接口引入了一些新功能——默认方法和静态方法。我们可以在Java SE 8的接口中编写方法实现,仅仅需要使用 default 关键字来定义它们。
Java 9 不仅像 Java 8 一样支持接口默认方法,同时还支持私有方法。在 Java 9 中,一个接口中能定义如下几种变量/方法:
- 常量
- 抽象方法
- 默认方法
- 静态方法
- 私有方法
- 私有静态方法
1 | public interface Employee { |
字符串增强 @since 11
Java 11 增加了一系列的字符串处理方法,如以下所示:
1 | // 判断字符串是否为空白 |
集合工厂方法 @since 9
自 Java 9 开始,Jdk 里面为集合(List/ Set/ Map)都添加了 of 和 copyOf 方法,它们两个都用来创建不可变的集合,来看下它们的使用和区别。
1 | // 不可变集合 List |
注意:使用 of 创建的集合为不可变集合,不能进行添加、删除、替换、排序等操作,不然会报 java.lang.UnsupportedOperationException 异常,使用 Set.of() 不能出现重复元素、Map.of() 不能出现重复 key,否则回报 java.lang.IllegalArgumentException。
List.of 和 Arrays.asList 区别 @since 9
1、Arrays.asList 返回可变的 list,而 List.of 返回的是不可变的 list
1 | List<Integer> list = Arrays.asList(1, 2, null); |
2、Arrays.asList 支持 null,而 List.of 不行
1 | List<Integer> list = Arrays.asList(1, 2, null); // OK |
3、List.of 和 Arrays.asList 的 contains 方法对 null 处理不一样
1 | List<Integer> list = Arrays.asList(1, 2, 3); |
4、Arrays.asList 的数组的修改会影响原数组
1 | Integer[] array = {1, 2, 3}; |
使用 copyOf 方法创建不可变集合 @since 10
1 | // 不可变集合 List |
来看下它们的源码:
1 | static <E> List<E> of(E... elements) { |
可以看出 copyOf 方法会先判断来源集合是不是 AbstractImmutableList 类型的,如果是,就直接返回,如果不是,则调用 of 创建一个新的集合。
Stream 增强 @since 9
Stream 是 Java 8 中的新特性,Java 9 开始对 Stream 增加了以下 4 个新方法。
(1) 增加单个参数构造方法,可为 null,此方法可以接收 null 来创建一个空流
1 | long count = Stream.ofNullable(null).count(); |
(2)增加 takeWhile 和 dropWhile 方法
1 | // 从开始计算,当 n < 3 时就截止:此方法根据 Predicate 接口来判断如果为 true 就取出来生成一个新的流,只要碰到 false 就终止,不管后边的元素是否符合条件。 |
(3)iterate 重载
以前使用 iterate 方法生成无限流需要配合 limit 进行截断
1 | List<Integer> collect2 = Stream.iterate(1, i -> i + 1) |
现在重载后这个方法增加了个判断参数
1 | List<Integer> collect3 = Stream.iterate(1, i -> i <= 5, i -> i + 1) |
Optional 增强 @since 9
Opthonal 也增加了几个非常酷的方法,现在可以很方便的将一个 Optional 转换成一个 Stream,或者当一个空 Optional 时给它一个替代的。
(1)stream():stream 方法的作用就是将 Optional 转为一个 Stream,如果该 Optional 中包含值,那么就返回包含这个值的 Stream,否则返回一个空的 Stream(Stream.empty())。
1 | // 返回Optional值的流 |
(2)ifPresentOrElse(Consumer< ? super T> action, Runnable emptyAction):ifPresentOrElse 方法的改进就是有了 else,接受两个参数 Consumer 和 Runnable。ifPresentOrElse 方法的用途是,如果一个 Optional 包含值,则对其包含的值调用函数 action,即 action.accept(value),这与 ifPresent 一致;与 ifPresent 方法的区别在于,ifPresentOrElse 还有第二个参数 emptyAction —— 如果 Optional 不包含值,那么 ifPresentOrElse 便会调用 emptyAction,即 emptyAction.run()。
1 | Optional<Integer> optional = Optional.of(1); |
(3)or(Supplier< ? extends Optional< ? extends T>> supplier):如果值存在,返回 Optional 指定的值,否则返回一个预设的值。
1 | Optional<String> optional1 = Optional.of("Mahesh"); |
标准 HTTP Client 升级 @since 11
Java 11 对 Java 9 中引入并在 Java 10 中进行了更新的 Http Client API 进行了标准化,在前两个版本中进行孵化的同时,Http Client 几乎被完全重写,并且现在完全支持异步非阻塞。
Java 11 中的新 Http Client API,提供了对 HTTP/2 等业界前沿标准的支持,同时也向下兼容 HTTP/1.1,精简而又友好的 API 接口,与主流开源 API(如:Apache HttpClient、Jetty、OkHttp 等)类似甚至拥有更高的性能。与此同时它是 Java 在 Reactive-Stream 方面的第一个生产实践,其中广泛使用了 Java Flow API,终于让 Java 标准 HTTP 类库在扩展能力等方面,满足了现代互联网的需求,是一个难得的现代 Http/2 Client API 标准的实现,Java 工程师终于可以摆脱老旧的 HttpURLConnection 了。
同步请求会阻止当前线程
1 | var request = HttpRequest.newBuilder() |
异步请求不会阻止当前线程,而是返回 CompletableFuture 来进行异步操作
1 | var client = HttpClient.newHttpClient(); |
简化启动单个源代码文件的方法 @since 11
Java 11 版本中最令人兴奋的功能之一是增强 Java 启动器,使之能够运行单一文件的 Java 源代码。此功能允许使用 Java 解释器直接执行 Java 源代码。源代码在内存中编译,然后由解释器执行。唯一的约束在于所有相关的类必须定义在同一个 Java 文件中。
如今单文件程序在编写小实用程序时很常见,特别是脚本语言领域。从中开发者可以省去用 Java 编译程序等不必要工作,以及减少新手的入门障碍。
举个例子,写一个类文件 HelloWorld.java
1 | public class HelloWorld { |
以前简化启动单个源代码文件需要这样运行
1 | $ javac HelloWorld.java |
现在只需要这样1
2$ java HelloWorld.java
Hello World
更多新特性
- Flow API for reactive programming
- Java Module System
- Application Class Data Sharing
- Dynamic Class-File Constants
- Java REPL (JShell)
- Flight Recorder
- Unicode 10
- G1: Full Parallel Garbage Collector
- ZGC: Scalable Low-Latency Garbage Collector
- Epsilon: No-Op Garbage Collector
- Deprecate the Nashorn JavaScript Engine
- …