以匠人之心,Java 11 正式发布

背景

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 年。

Oracle Java 支持版本

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
2
3
4
$ jshell
| 欢迎使用 JShell -- 版本 13.0.1
| 要大致了解该版本, 请键入: /help intro
jshell>

2、查看 JShell 命令:输入 /help 可以查看 JShell 相关的命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
jshell> /help
| 键入 Java 语言表达式, 语句或声明。
| 或者键入以下命令之一:
| /list [<名称或 id>|-all|-start]
| 列出您键入的源
| /edit <名称或 id>
| 编辑源条目
| /drop <名称或 id>
| 删除源条目
| /save [-all|-history|-start] <文件>
| 将片段源保存到文件
| /open <file>
| 打开文件作为源输入
| /vars [<名称或 id>|-all|-start]
| 列出已声明变量及其值
| /methods [<名称或 id>|-all|-start]
| 列出已声明方法及其签名
| /types [<名称或 id>|-all|-start]
| 列出类型声明
| /imports
| 列出导入的项
| /exit [<integer-expression-snippet>]
| 退出 jshell 工具
| /env [-class-path <路径>] [-module-path <路径>] [-add-modules <模块>] ...
| 查看或更改评估上下文
| /reset [-class-path <路径>] [-module-path <路径>] [-add-modules <模块>]...
| 重置 jshell 工具
| /reload [-restore] [-quiet] [-class-path <路径>] [-module-path <路径>]...
| 重置和重放相关历史记录 -- 当前历史记录或上一个历史记录 (-restore)
| /history [-all]
| 您键入的内容的历史记录
| /help [<command>|<subject>]
| 获取有关使用 jshell 工具的信息
| /set editor|start|feedback|mode|prompt|truncation|format ...
| 设置配置信息
| /? [<command>|<subject>]
| 获取有关使用 jshell 工具的信息
| /!
| 重新运行上一个片段 -- 请参阅 /help rerun
| /<id>
| 按 ID 或 ID 范围重新运行片段 -- 参见 /help rerun
| /-<n>
| 重新运行以前的第 n 个片段 -- 请参阅 /help rerun
|
| 有关详细信息, 请键入 '/help', 后跟
| 命令或主题的名称。
| 例如 '/help /list' 或 '/help intro'。主题:
|
| intro
| jshell 工具的简介
| keys
| 类似 readline 的输入编辑的说明
| id
| 片段 ID 以及如何使用它们的说明
| shortcuts
| 片段和命令输入提示, 信息访问以及
| 自动代码生成的按键说明
| context
| /env /reload 和 /reset 的评估上下文选项的说明
| rerun
| 重新评估以前输入片段的方法的说明

3、JShell 执行使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// JShell 执行计算
jshell> 3 + 1
$3 ==> 4

// JShell 定义变量
jshell> var uuid = UUID.randomUUID();
uuid ==> f543a51c-8166-49c3-a0d0-c06ed5993f71

// 输出的表达式
jshell> System.out.println("Hello World!");
Hello World!

// JShell 创建函数

jshell> public String say(String s) {
...> return s;
...> }
| 已创建 方法 say(String)

// JShell 使用函数
jshell> say("Hello World!");
$8 ==> "Hello World!"

4、退出 JShell:输入 /exit 命令退出 jshell

1
2
jshell> /exit
| 再见

局部变量类型推断 @since 10

从 Java 10 开始,便引入了局部变量类型推断这一关键特性。类型推断允许使用关键字 var 作为局部变量的类型而不是实际类型,编译器根据分配给变量的值推断出类型。这一改进简化了代码编写、节省了开发者的工作时间,因为不再需要显式声明局部变量的类型,而是可以使用关键字 var,且不会使源代码过于复杂。var 是 Java10 中新增的局部类型变量推断。它会根据后面的值来推断变量的类型,所以 var 必须要初始化。var 其实就是 Java 10 增加的一种语法糖而已,在编译期间会自动推断实际类型,其编译后的字节码和实际类型一致。

但是在 Java 10 中,还有下面几个限制:

  • 只能用于局部变量上
  • 声明时必须初始化
  • 不能用作方法参数
  • 不能在 Lambda 表达式中使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// var 定义局部变量[等同于 int a = 1;]
var a = 1;

// var 接收方法返回[等同于 String result = this.getResult();]
var result = this.getResult();

// var 循环中定义局部变量
for (var i = 0; i < 5; i++) {
System.out.println(i);
}

// var 结合泛型[等同于 List<String> list = new ArrayList<>();]
var list = new ArrayList<String>();

// var 结合泛型[<>里默认会是Object]
var list2 = new ArrayList<>();

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

Java 11 与 Java 10 的不同之处在于允许开发者在 Lambda 表达式中使用 var 进行参数声明。乍一看,这一举措似乎有点多余,因为在写代码过程中可以省略 Lambda 参数的类型,并通过类型推断确定它们。但是,添加上类型定义同时使用 @Nonnull 和 @Nullable 等类型注释还是很有用的,既能保持与局部变量的一致写法,也不丢失代码简洁。

1
2
@Nonnull var x = new Foo();
(@Nonnull var x, @Nullable var y) -> x.process(y)

局部变量类型推断优缺点

(1)优点:简化代码

1
2
3
4
5
6
7
CopyOnWriteArrayList list1 = new CopyOnWriteArrayList();
ConcurrentModificationException cme1 = new ConcurrentModificationException();
DefaultServiceUnavailableRetryStrategy strategy1 = new DefaultServiceUnavailableRetryStrategy();

var list2 = new CopyOnWriteArrayList <>();
var cme2 = new ConcurrentModificationException ();
var strategy2 = new DefaultServiceUnavailableRetryStrategy ();

从以上代码可以看出,很长的定义类型会显得代码很冗长,使用 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
2
3
4
5
6
7
8
9
10
public interface Employee {

private Long createEmployeeID() {
// Method implementation goes here.
}

private static void displayEmployeeDetails() {
// Method implementation goes here.
}
}

字符串增强 @since 11

Java 11 增加了一系列的字符串处理方法,如以下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 判断字符串是否为空白
boolean blank = " ".isBlank();
System.out.println(blank); // true

// 去除首尾空格
String strip = " Hello Java11 ".strip();
System.out.println(strip); // "Hello Java11"

// 去除尾部空格
String stripTrailing = " Hello Java11 ".stripTrailing();
System.out.println(stripTrailing); // " Hello Java11"

// 去除首部空格
String stripLeading = " Hello Java11 ".stripLeading();
System.out.println(stripLeading); // "Hello Java11 "

// 复制字符串
String repeat = "Java11".repeat(3);
System.out.println(repeat); // "Java11Java11Java11"

// 行数统计
long count = "A\nB\nC".lines().count();
System.out.println(count); // 3

集合工厂方法 @since 9

自 Java 9 开始,Jdk 里面为集合(List/ Set/ Map)都添加了 of 和 copyOf 方法,它们两个都用来创建不可变的集合,来看下它们的使用和区别。

1
2
3
4
5
6
7
8
// 不可变集合 List
var list = List.of("Java", "Python", "C");

// 不可变集合 Set
var set = Set.of("Java", "Python", "C");

// 不可变集合 Map
var map = Map.of("Java", 1, "Python", 2, "C", 3);

注意:使用 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
2
3
4
List<Integer> list = Arrays.asList(1, 2, null);
list.set(1, 10); // OK
List<Integer> list = List.of(1, 2, 3);
list.set(1, 10); // Fails

2、Arrays.asList 支持 null,而 List.of 不行

1
2
List<Integer> list = Arrays.asList(1, 2, null); // OK
List<Integer> list = List.of(1, 2, null); // 异常:NullPointerException

3、List.of 和 Arrays.asList 的 contains 方法对 null 处理不一样

1
2
3
4
List<Integer> list = Arrays.asList(1, 2, 3);
list.contains(null); // Return false
List<Integer> list = List.of(1, 2, 3);
list.contains(null); // 抛出NullPointerException异常

4、Arrays.asList 的数组的修改会影响原数组

1
2
3
4
5
6
7
8
Integer[] array = {1, 2, 3};
List<Integer> list = Arrays.asList(array);
array[1] = 10;
System.out.println(list); // 输出 [1, 10, 3]
Integer[] array = {1, 2, 3};
List<Integer> list = List.of(array);
array[1] = 10;
System.out.println(list); // 输出 [1, 2, 3]

使用 copyOf 方法创建不可变集合 @since 10

1
2
3
4
5
6
7
8
9
// 不可变集合 List
var immutableList = List.of("Java", "Python", "C");
var copyImmutableList = List.copyOf(immutableList);
System.out.println(immutableList == copyImmutableList); // true

// 正常集合 List:用的 new 创建的集合,不属于不可变 AbstractImmutableList 类的子类,所以 copyOf 方法又创建了一个新的实例,所以为false
var list = new ArrayList<String>();
var copy = List.copyOf(list);
System.out.println(list == copy); // false

来看下它们的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static <E> List<E> of(E... elements) {
switch (elements.length) { // implicit null check of elements
case 0:
return ImmutableCollections.emptyList();
case 1:
return new ImmutableCollections.List12<>(elements[0]);
case 2:
return new ImmutableCollections.List12<>(elements[0], elements[1]);
default:
return new ImmutableCollections.ListN<>(elements);
}
}

static <E> List<E> copyOf(Collection<? extends E> coll) {
return ImmutableCollections.listCopy(coll);
}

static <E> List<E> listCopy(Collection<? extends E> coll) {
if (coll instanceof AbstractImmutableList && coll.getClass() != SubList.class) {
return (List<E>)coll;
} else {
return (List<E>)List.of(coll.toArray());
}
}

可以看出 copyOf 方法会先判断来源集合是不是 AbstractImmutableList 类型的,如果是,就直接返回,如果不是,则调用 of 创建一个新的集合。

Stream 增强 @since 9

Stream 是 Java 8 中的新特性,Java 9 开始对 Stream 增加了以下 4 个新方法。

(1) 增加单个参数构造方法,可为 null,此方法可以接收 null 来创建一个空流

1
2
long count = Stream.ofNullable(null).count();
System.out.println(count); // 0

(2)增加 takeWhile 和 dropWhile 方法

1
2
3
4
5
6
7
8
9
10
11
// 从开始计算,当 n < 3 时就截止:此方法根据 Predicate 接口来判断如果为 true 就取出来生成一个新的流,只要碰到 false 就终止,不管后边的元素是否符合条件。
List<Integer> collect = Stream.of(1, 2, 3, 2, 1)
.takeWhile(n -> n < 3)
.collect(Collectors.toList());
System.out.println(collect.toString()); // [1, 2]

// 一旦 n < 3 不成立就开始计算:此方法根据 Predicate 接口来判断如果为 true 就丢弃,只要碰到 false 就来生成一个新的流,不管后边的元素是否符合条件。
List<Integer> collect1 = Stream.of(1, 2, 3, 2, 1)
.dropWhile(n -> n < 3)
.collect(Collectors.toList());
System.out.println(collect1.toString()); // [3, 2, 1]

(3)iterate 重载

以前使用 iterate 方法生成无限流需要配合 limit 进行截断

1
2
3
4
List<Integer> collect2 = Stream.iterate(1, i -> i + 1)
.limit(5)
.collect(Collectors.toList());
System.out.println(collect2.toString()); // [1, 2, 3, 4, 5]

现在重载后这个方法增加了个判断参数

1
2
3
List<Integer> collect3 = Stream.iterate(1, i -> i <= 5, i -> i + 1)
.collect(Collectors.toList());
System.out.println(collect3.toString()); // [1, 2, 3, 4, 5]

Optional 增强 @since 9

Opthonal 也增加了几个非常酷的方法,现在可以很方便的将一个 Optional 转换成一个 Stream,或者当一个空 Optional 时给它一个替代的。

(1)stream():stream 方法的作用就是将 Optional 转为一个 Stream,如果该 Optional 中包含值,那么就返回包含这个值的 Stream,否则返回一个空的 Stream(Stream.empty())。

1
2
3
4
5
6
7
// 返回Optional值的流
Stream<String> stream = Optional.of("Java 11").stream();
stream.forEach(System.out::println); // Java 11

// 返回空流
Stream<Object> stream2 = Optional.empty().stream();
stream2.forEach(System.out::println); //

(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
2
3
4
5
6
7
Optional<Integer> optional = Optional.of(1);
optional.ifPresentOrElse(x -> System.out.println("Value: " + x),
() -> System.out.println("Not Present.")); // Value: 1

optional = Optional.empty();
optional.ifPresentOrElse(x -> System.out.println("Value: " + x),
() -> System.out.println("Not Present.")); //Not Present.

(3)or(Supplier< ? extends Optional< ? extends T>> supplier):如果值存在,返回 Optional 指定的值,否则返回一个预设的值。

1
2
3
4
5
6
7
Optional<String> optional1 = Optional.of("Mahesh");
Supplier<Optional<String>> supplierString = () -> Optional.of("Not Present");
optional1 = optional1.or(supplierString);
optional1.ifPresent(x -> System.out.println("Value: " + x)); // Value: Mahesh
optional1 = Optional.empty();
optional1 = optional1.or(supplierString);
optional1.ifPresent(x -> System.out.println("Value: " + x)); // Value: Not Present

标准 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
2
3
4
5
6
7
8
var request = HttpRequest.newBuilder()
.uri(URI.create("https://www.baidu.com/"))
.GET()
.build();
var client = HttpClient.newHttpClient();
// 同步
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());

异步请求不会阻止当前线程,而是返回 CompletableFuture 来进行异步操作

1
2
3
4
5
6
7
8
9
10
11
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder()
.uri(URI.create("https://www.baidu.com/"))
// 省略.GET(),因为它是默认的请求方法。
// .GET()
.build();
// 异步
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenAccept(System.out::println)
.join();

简化启动单个源代码文件的方法 @since 11

Java 11 版本中最令人兴奋的功能之一是增强 Java 启动器,使之能够运行单一文件的 Java 源代码。此功能允许使用 Java 解释器直接执行 Java 源代码。源代码在内存中编译,然后由解释器执行。唯一的约束在于所有相关的类必须定义在同一个 Java 文件中。

如今单文件程序在编写小实用程序时很常见,特别是脚本语言领域。从中开发者可以省去用 Java 编译程序等不必要工作,以及减少新手的入门障碍。

举个例子,写一个类文件 HelloWorld.java

1
2
3
4
5
6
public class HelloWorld {

public static void main(String[] args) {
System.out.println("Hello World!");
}
}

以前简化启动单个源代码文件需要这样运行

1
2
3
$ javac HelloWorld.java
$ java HelloWorld
Hello World

现在只需要这样

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

参考博文

[1]. Java 11 正式发布,这 8 个逆天新特性教你写出更牛逼的代码
[2]. Java 11 新特性介绍

谢谢你长得那么好看,还打赏我!😘
0%