blog/source/_posts/nf-java9.md

127 lines
7.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: Java 9的新特性
tags:
- JVM
- Java
- Java 9
- Optional
- JPMS
- 新特性
categories:
- - JVM
- Java
keywords: ''
date: 2021-05-20 15:15:29
---
Java 9在Java 8的基础上做了许多重大的改进尤其是引入了模块系统这直接导致使用Java 8的程序在Java 9中可能无法通过编译。除此之外Java 9还对Java 8中引入的功能做了一些增补和改进。<!-- more -->
本系列的文章有:
- {% post_link nf-java8 %}
- {% post_link nf-java9 %}
- {% post_link nf-java10 %}
- {% post_link nf-java11 %}
- {% post_link nf-java12 %}
- {% post_link nf-java13 %}
- {% post_link nf-java14 %}
- {% post_link nf-java15 %}
- {% post_link nf-java16 %}
## Optional
Java 9为Optional类增加了以下三个方法。
* `stream()`将一个Optional转换为一个Stream如果Optional为空则Stream将不包含任何值。
* `ifPresentOrElse(Consumer<T>, Runnable)`如果Optional包含值就用其中的值调用Consumer函数否则就启动一个任务。
* `or(Supplier<Optional<T>>)`如果值存在就返回Optional中的值否则返回预设值。
## Stream
Stream类现在也增加了一些更加便利的方法。
* `takeWhile(Predicate<T>)`从流中返回尽可能多的元素直到Predicate返回`false`为止。
* `dropWhile(Predicate<T>)`从流中丢弃尽可能多的元素直到Predicate返回`false`为止。
* `iterate(T, Predicate<T>, UnaryOperator<T>)`,允许使用初始种子使用`UnaryOperator`函数创建无限流,直到`Predicate`返回`false`为止。
* `ofNullable(T)`用可空对象创建一个只有单个元素的流可以通过检查流来预防NullPointException。
## 私有接口方法
Java 8引入默认方法之后在接口中可以定义常量、抽象方法、默认方法和静态方法。Java 9在这个基础上引入了私有方法从而为接口增加了私有方法和私有静态方法。接口中的私有方法和私有静态方法使用`private`关键字修饰。`private`关键字在接口中不能用来修饰抽象方法。
接口中的私有方法仅能够提供接口本身使用,不能通过接口访问或者通过继承从另一个接口或者类来访问。
## 资源操作
资源操作try-with-resources是Java 7中引入的新的异常处理机制。这里的资源是指在被程序使用后必须要被关闭的对象。资源操作确保了每个被使用的资源都能够在语句块结束时关闭。所有实现了`java.lang.AutoCloseable`接口(包括`java.io.Closeable`接口)的对象都可以作为资源使用。
在Java 7语法规则中资源变量必须在资源操作语句中声明例如以下示例。
```java
String readData(String message) throws IOException {
Reader input = new StringReader(message);
BufferedReader reader = new BufferReader(input);
// 这里需要重新声明一个变量,不能直接使用外部作用域定义的变量
try (BufferedReader reader1 = reader) {
return reader1.readLine();
}
}
```
在Java 9 中如果这个资源是final的或者等效于final那么就无需重新声明一个变量而直接使用了所以以上示例可以改写为以下形式。
```java
String readData(String message) throws IOException {
Reader input = new StringReader(message);
BufferedReader reader = new BufferReader(input);
// 这里无需重新声明一个变量
try (reader) {
return reader.readLine();
}
}
```
> final变量表示常量只能被赋值一次其值不可再更改。所以只做读取使用和无其他副作用的局部变量会被作为final变量优化处理。
## 模块系统
模块系统是Java 9所做的最大改变模块是代码和数据的封装其中代码被组织为多个包数据则包含资源文件和其他静态信息。Java 9编译得到的最终Artifact可以是Jar文件也可以是模块系统引入的新增的JMOD文件。
模块的根目录中需要放置一个`module-info.java`文件来描述模块该文件在编译后位于模块Artifact的根目录中。在这个文件中可以使用关键字`module`声明一个模块,例如以下示例。
```java
module xyz.archgrid.samplemodule {
requires java.base; // 其实这一条引用是不必书写的,这里只是做一个示例
}
```
模块中的其他代码与之前版本Java的代码组织形式相同。但是在编译时又不同了需要使用`javac -d 模块输出目录`。而执行一个模块则需要使用命令`java --module-path 模块所在目录 -m 模块名/报名.主类名`。
传统Java程序打包后是使用`--class-path`(简写为`-cp`来指定Jar包所在位置的。例如`java --class-path sample.jar some.package.MainClass`。运行模块则是用`--module-path`(简写为`-p`)来指定模块的位置。
模块的出现使得构建和维护一个大型库或者应用变得更加容易并且提升了Java的性能使得应用不需要使用大段的CLASS-PATH来引用过量的依赖。
模块描述中可以使用以下关键字来控制其他应用对包内内容的访问。
* `exports`,选择需要导出的类。
* `requires`,引用其他的模块。需要注意的是,`java.base`会被默认加入引用,不需要显式书写。每个模块仅会被引入一次,并且只能访问已经导出的类。
被依赖模块可以通过命令`jar --create --file 目标jar文件 -C 已编译目录 要打包内容`来完成打包。依赖其他模块的模块可以通过命令`javac -p 依赖模块目录 -d 输出目录 源文件集`来完成编译,并使用命令`jar --create -f 目标jar文件 -p 依赖模块目录 --main-class 主类 -C 已编译目录 要打包内容`。在运行最终的模块时需要指定所有模块的位置,例如`java -p 主模块:依赖模块 -m 主类`。
## 兼容Jar包
兼容Jar包可以允许创建针对不同的Java环境选择不同class版本的Jar包。要创建兼容Jar包需要在`MANIFEST.MF`文件中使用增加的属性:`Multi-Release: true`。此外还需要在`META-INF`目录中增加一个名为`versions`目录用于存放针对不同版本JRE的class文件。
编译用于不同版本JRE的class文件可以使用`javac --release n`命令,其中参数`n`为目标版本号例如7、8、9等。打包Jar时可以使用命令`jar -c -f 输出文件 -C 源目录 . --release 额外版本号 -C 额外版本源目录 .`。
## 其他新特性
Java 9中还有一些新特性比较简单属于语法功能的增补。
* 匿名类中可以使用钻石操作符`<>`了。
* 集合工厂增加了`of()`、`ofEntries()`等可接受更多参数的重载方法来快速创建集合。
* 增加了`ProcessHandle`接口来增强对本地进程的支持,允许查询进程状态并管理进程。
* 除原有的`@Deprecated`注解上增加了字符串型参数`since`来标注API被弃用的版本布尔型参数`forRemoval`来标注API将在未来被删除。
* 将Java 8中引入的`CompletableFuture<T>`类进行了扩展,增加了延迟和超时等功能,并添加了新的工厂方法。