Java 9 - 新特性
Java 9(也称为 JDK 1.9)是 Java 编程语言开发的一个主要版本。其初始版本于 2017 年 9 月 21 日发布。Java 9 发布的主要目标是 −
使 JDK 和 Java 标准版平台模块化,从而能够很好地扩展到小型计算设备。
提升 JDK 和 Java 实现的整体安全性。
使 Java SE 和 EE 平台的代码库和大型应用程序的构建过程和维护变得更容易。
为 Java 平台设计并实现一个标准模块系统,便于在平台和 JDK 上轻松应用。
以下是 Java 9 支持的新特性列表:
模块系统
模块系统被引入,以将 Java 代码的模块化提升到新水平。模块是一个自描述的代码和数据集合。模块可以包含包、特定功能的配置。模块为其内容提供更好的访问控制。从 Java 9 开始,Java 库被划分为多个模块,可以使用以下命令查看。
C:\Users\Mahesh>java --list-modules java.base@20.0.2 java.compiler@20.0.2 java.datatransfer@20.0.2 java.desktop@20.0.2 ... jdk.xml.dom@20.0.2 jdk.zipfs@20.0.2
示例 - 使用模块
下面的代码片段定义了应用程序根文件夹中 module-info.java 文件中声明的模块。
module com..greetings {
requires com..util;
requires static com..logging;
requires transitive com..base;
exports com..greetings.HelloWorld;
opens com..greetings.HelloWorld;
}
在这里,我们声明我们的模块依赖于三个模块,并向外部世界导出公共 class,并允许反射检查特定 class。默认情况下,模块的私有成员无法通过反射访问。
REPL
REPL 代表 Read Evaluate Print Loop(读取-求值-打印循环)。在 Java 9 中引入了 JShell REPL 引擎,作为一个交互式控制台,可以在不保存和编译 Java 代码文件的情况下运行任意 Java 代码片段。JShell 读取输入的每一行,求值,然后打印结果,并准备好接收下一组输入。
示例 - 使用 JShell 作为 REPL
下面的代码片段展示了如何在 JShell 中创建变量,分号是可选的。我们也可以在 JShell 中创建对象。如果变量未初始化,则会赋予默认值,或者如果是对象引用则为 null。一旦创建变量,就可以像最后一行所示使用它,我们使用了字符串变量来打印其值。
示例
在以下示例中,我们创建了变量、求值表达式、创建了日期对象。
jshell> int i = 10
i ==> 10
jshell> String name = "Mahesh";
name ==> "Mahesh"
jshell> Date date = new Date()
date ==> Fri Feb 02 14:52:49 IST 2024
jshell> String.format("%d pages read.", 10);
$9 ==> "10 pages read."
jshell> $9
$9 ==> "10 pages read."
jshell> name
name ==> "Mahesh"
改进的 JavaDocs
从 Java 9 开始,Java 现在支持 HTML5 输出生成,并为生成的 API 文档提供搜索框。
示例
在本示例中,我们将创建一个符合 HTML5 标准的 javadoc。
考虑 C:/JAVA 文件夹中的以下代码。
Tester.java
/**
* @author MahKumar
* @version 0.1
*/
public class Tester {
/**
* 默认方法,用于运行并打印
* <p>Hello world</p>
* @param args 命令行参数
*/
public static void main(String []args) {
System.out.println("Hello World");
}
}
使用 jdk 9 的 javadoc 工具并带上 -html5 标志来生成新型文档。
C:\JAVA> javadoc -d C:/JAVA -html5 Tester.java Loading source file Tester.java... Constructing Javadoc information... Standard Doclet version 9.0.1 Building tree for all the packages and classes... Generating C:\JAVA\Tester.html... Generating C:\JAVA\package-frame.html... Generating C:\JAVA\package-summary.html... Generating C:\JAVA\package-tree.html... Generating C:\JAVA\constant-values.html... Building index for all the packages and classes... Generating C:\JAVA\overview-tree.html... Generating C:\JAVA\index-all.html... Generating C:\JAVA\deprecated-list.html... Building index for all classes... Generating C:\JAVA\allclasses-frame.html... Generating C:\JAVA\allclasses-frame.html... Generating C:\JAVA\allclasses-noframe.html... Generating C:\JAVA\allclasses-noframe.html... Generating C:\JAVA\index.html... Generating C:\JAVA\help-doc.html...
它将在 D:/test 目录中创建更新的 Java 文档页面,您将看到以下输出。
多发布 JAR
Java 9 中的多发布 JAR 特性增强了 JAR 格式,使得单个存档中可以共存多个特定 Java 版本的 class 文件。
在多发布 JAR 格式中,一个 jar 文件可以包含不同版本的 Java 类或资源,这些可以根据平台进行维护和使用。在 JAR 中,MANIFEST.MF 文件在其主节中有一个条目 Multi-Release: true。META-INF 目录还包含一个 versions 子目录,其子目录(从 9 开始对应 Java 9)存储特定版本的类和资源文件。
使用 MANIFEST.MF,我们可以指定 Java 9 或更高版本特定类在单独位置,如下所示 −
Java 多发布 JAR 文件目录结构示例
jar root
- Calculator.class
- Util.class
- Math.class
- Service.class
META-INF
- versions
- 9
- Util.class
- Math.class
- 10
- Util.class
- Math.class
现在如果 JRE 不支持多发布 JAR,那么它将选择根级别的类来加载和执行,否则将加载特定版本的类。例如,如果上述 JAR 在 Java 8 中使用,则将使用根级别的 Util.class。如果同一个 JAR 由 Java 9 执行,则将选择 Java 9 特定版本的类,依此类推。这样,第三方库/框架可以在不更改针对较低版本编写的源代码的情况下支持新特性。
集合工厂方法改进
在 Java 9 中,为 List、Set 和 Map 接口添加了新的静态工厂方法,用于创建这些集合的不可变实例。这些工厂方法主要是为了以更简洁、不冗长的方式创建集合。
Java 9 之前 List 接口工厂方法的示例
这里,我们在 Java 9 之前创建不可变 list。
package com.;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Tester {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Java");
list.add("HTML 5");
list.add("C");
list = Collections.unmodifiableList(list);
System.out.println(list);
}
}
让我们编译并运行上述程序,这将产生以下结果 −
[Java, HTML 5, C]
Java 9 中 List 接口工厂方法的示例
这里,我们在 Java 9 中创建不可变 list。
package com.;
import java.util.List;
public class Tester {
public static void main(String[] args){
List<String> list = List.of("Java","HTML 5","C");
System.out.println(list);
}
}
让我们编译并运行上述程序,这将产生以下结果 −
[Java, HTML 5, C]
私有接口方法
Java 9 引入了私有方法和静态私有接口方法。作为私有方法,此类方法无法通过实现类或子接口访问。这些方法被引入是为了实现封装,将某些方法的实现仅保留在接口中。它有助于减少重复代码、提高可维护性并编写干净的代码。
示例 - Java 9 中接口的私有方法
package com.;
interface util {
public default int operate(int a, int b) {
return sum(a, b);
}
private int sum(int a, int b) {
return a + b;
}
}
public class Tester implements util {
public static void main(String[] args) {
Tester tester = new Tester();
System.out.println(tester.operate(2, 3));
}
}
输出
让我们编译并运行上述程序,这将产生以下结果 −
5
类似地,我们可以拥有私有静态方法,该方法可以从静态和非静态方法中调用。
Process API 改进
在 Java 9 中,负责控制和管理操作系统进程的 Process API 得到了显著改进。ProcessHandle 类现在提供了进程的原生进程 ID、启动时间、累计 CPU 时间、参数、命令、用户、父进程以及子进程。ProcessHandle 类还提供了检查进程存活状态和销毁进程的方法。它具有 onExit 方法,CompletableFuture 类可以在进程退出时异步执行操作。
生成新进程示例
在这个示例中,我们使用 ProcessBuilder 为 notepad 创建了一个新进程并启动它。使用 ProcessHandle.Info 接口,我们获取了新生成进程的进程信息。
package com.;
import java.time.ZoneId;
import java.util.stream.Stream;
import java.util.stream.Collectors;
import java.io.IOException;
public class Tester {
public static void main(String[] args) throws IOException {
ProcessBuilder pb = new ProcessBuilder("notepad.exe");
String np = "Not Present";
Process p = pb.start();
ProcessHandle.Info info = p.info();
System.out.printf("Process ID : %s%n", p.pid());
System.out.printf("Command name : %s%n", info.command().orElse(np));
System.out.printf("Command line : %s%n", info.commandLine().orElse(np));
System.out.printf("Start time: %s%n",
info.startInstant().map(i -> i.atZone(ZoneId.systemDefault())
.toLocalDateTime().toString()).orElse(np));
System.out.printf("Arguments : %s%n",
info.arguments().map(a -> Stream.of(a).collect(
Collectors.joining(" "))).orElse(np));
System.out.printf("User : %s%n", info.user().orElse(np));
}
}
输出
你会看到类似输出。
Process ID : 5580 Command name : C:\Program Files\WindowsApps\Microsoft.WindowsNotepad_11.2401.26.0_x64__8wekyb3d8bbwe\Notepad\Notepad.exe Command line : Not Present Start time: 2024-04-02T17:07:14.305 Arguments : Not Present User : DESKTOP\
Stream API 改进
Stream 在 Java 8 中引入,以帮助开发者对对象序列执行聚合操作。在 Java 9 中,添加了一些新方法来进一步改进 Stream。
takeWhile(Predicate 接口) 方法
语法
default Stream<T> takeWhile(Predicate<? super T> predicate)
takeWhile 方法会获取谓词返回 false 之前的所有值。对于有序 Stream,它返回一个 Stream,该 Stream 包含从此 Stream 中取出的最长前缀元素,这些元素匹配给定的谓词。
dropWhile(Predicate 接口)
语法
default Stream<T> dropWhile(Predicate<? super T> predicate)
dropWhile 方法会丢弃谓词返回 true 之前开头的所有值。对于有序 Stream,它返回一个 Stream,该 Stream 包含丢弃匹配给定谓词的最长前缀元素后此 Stream 的剩余元素。
iterate 方法
语法
static <T> Stream<T> iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> next)
iterate 方法现在将 hasNext 谓词作为参数,一旦 hasNext 谓词返回 false 就会停止循环。
ofNullable
语法
static <T> Stream<T> ofNullable(T t)
ofNullable 方法用于防止 NullPointerException 并避免对 Stream 进行 null 检查。此方法如果参数非 null,则返回包含单个元素的顺序 Stream,否则返回空 Stream。
Try with Resources 改进
在 Java 9 之前,资源需要在 try 语句之前或 try 语句内部声明,如以下示例所示。在本示例中,我们将使用 BufferedReader 作为资源来读取字符串,然后关闭 BufferedReader。
Java 9 及以后版本
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
public class Tester {
public static void main(String[] args) throws IOException {
System.out.println(readData("test"));
}
static String readData(String message) throws IOException {
Reader inputString = new StringReader(message);
BufferedReader br = new BufferedReader(inputString);
try (br) {
return br.readLine();
}
}
}
输出
让我们编译并运行上述程序,这将产生以下结果 −
test
增强的 @Deprecated 注解
@Deprecated 注解在 Java 5 版本中引入。使用 @Deprecated 注解标记的程序元素表示由于以下任何原因不应使用它 −
- 其使用可能导致错误。
- 它在未来版本中可能不兼容。
- 它在未来版本中可能被移除。
- 有更好、更高效的替代方案取代了它。
每当使用已废弃的元素时,编译器都会生成警告。在 Java 9 中,对 @Deprecated 注解进行了两项新改进。
forRemoval − 表示该注解元素是否将在未来版本中被移除。默认值为 false。
since − 返回该注解元素被标记为已废弃的版本。默认值为空字符串。
带有 since 的已废弃
以下 Java 9 中 Boolean 类的 javadoc 示例说明了在 @Deprecated 注解上使用 since 属性的用法。
Boolean 类
带有 forRemoval 的已废弃
以下 Java 9 中 System 类的 javadoc 示例说明了在 @Deprecated 注解上使用 forRemoval 属性的用法。
System 类
内部类 Diamond 操作符
在 Java 9 中,diamond 操作符也可以与匿名类一起使用,以简化代码并提高可读性。
示例
在以下示例中,我们为抽象类 Handler 创建了匿名类,该类接受一个泛型参数,但在创建匿名类时不指定对象类型,因为我们不需要传递类型参数。编译器会自动推断类型。
public class Tester {
public static void main(String[] args) {
// 创建一个匿名类来处理 1
// 在这里,我们不需要在 diamond 操作符中传递类型参数
// 因为 Java 9 编译器可以自动推断类型
Handler<Integer> intHandler = new Handler<>(1) {
@Override
public void handle() {
System.out.println(content);
}
};
intHandler.handle();
Handler<? extends Number> intHandler1 = new Handler<>(2) {
@Override
public void handle() {
System.out.println(content);
}
};
intHandler1.handle();
Handler<?> handler = new Handler<>("test") {
@Override
public void handle() {
System.out.println(content);
}
};
handler.handle();
}
}
abstract class Handler<T> {
public T content;
public Handler(T content) {
this.content = content;
}
abstract void handle();
}
输出
让我们编译并运行上述程序,这将产生以下结果 −
1 2 Test
多分辨率图像 API
多分辨率图像 API 在 Java 9 中引入。该 API 支持具有不同分辨率变体的多个图像。该 API 允许将一组不同分辨率的图像用作单个多分辨率图像。
考虑以下图像。
这些是三个不同尺寸的 logo 图像。
现在,为了处理这三个图像,从 Java 9 开始,可以使用 Multi-resolution Image API 作为单个 API 来获取所有变体或特定变体以进行显示。
// 将所有图像读取到一个多分辨率图像中 MultiResolutionImage multiResolutionImage = new BaseMultiResolutionImage(images.toArray(new Image[0]));
这里 MultiResolutionImage 和 BaseMultiResolutionImage 类属于 java.awt.image 包。
以下是多分辨率图像的主要操作。
Image getResolutionVariant(double destImageWidth, double destImageHeight) − 获取特定图像,该图像是表示此逻辑图像的最佳变体,在指定尺寸下。
List<Image> getResolutionVariants() − 获取所有分辨率变体的可读列表。
示例 - 获取所有变体
在这个示例中,我们加载了三个图像并将它们存储在 MultiResolutionImage 中。然后使用 getResolutionVariants() 方法,检查此多分辨率图像中所有可用的图像变体并打印它们。
package com.;
import java.awt.Image;
import java.awt.image.BaseMultiResolutionImage;
import java.awt.image.MultiResolutionImage;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
public class Tester {
public static void main(String[] args) throws IOException, MalformedURLException {
// 准备所有图像的 URL 列表
List<String> imgUrls = List.of("https://www.example.com",
"https://www.example.com",
"https://www.example.com");
// 创建 Image 对象列表
List<Image> images = new ArrayList<Image>();
// 使用图像 URL 创建图像对象
for (String url : imgUrls) {
images.add(ImageIO.read(new URL(url)));
}
// 将所有图像读取到一个多分辨率图像中
MultiResolutionImage multiResolutionImage =
new BaseMultiResolutionImage(images.toArray(new Image[0]));
// 获取所有图像变体
List<Image> variants = multiResolutionImage.getResolutionVariants();
System.out.println("图像总数: " + variants.size());
// 打印所有图像
for (Image img : variants) {
System.out.println(img);
}
}
}
输出
让我们编译并运行上述程序,这将产生以下结果 −
Total number of images: 3 BufferedImage@7ce6a65d: type = 6 ColorModel: #pixelBits = 32 numComponents = 4 color space =java.awt.color.ICC_ColorSpace@548ad73b transparency = 3 has alpha = true isAlphaPre = false ByteInterleavedRaster: width =311 height = 89 #numDataElements 4 dataOff[0] = 3 BufferedImage@4c762604: type = 6 ColorModel: #pixelBits = 32 numComponents = 4 color space =java.awt.color.ICC_ColorSpace@548ad73b transparency = 3 has alpha = true isAlphaPre = false ByteInterleavedRaster: width =156 height = 45 #numDataElements 4 dataOff[0] = 3 BufferedImage@2641e737: type = 6 ColorModel: #pixelBits = 32 numComponents = 4 color space =java.awt.color.ICC_ColorSpace@548ad73b transparency = 3 has alpha = true isAlphaPre = false ByteInterleavedRaster: width =622 height = 178 #numDataElements 4 dataOff[0] = 3
CompletableFuture API 增强
CompletableFuture class 在 Java 8 中引入,用于表示可以通过显式设置其值和状态来完成的 Future。它可以作为 java.util.concurrent.CompletionStage 使用。它支持在 future 完成时触发的依赖函数和动作。在 Java 9 中,CompletableFuture API 得到了进一步增强。以下是 API 的相关变更。
- 支持延迟和超时。
- 改进对子类化的支持。
- 新增工厂方法。
支持延迟和超时
public CompletableFuture<T> completeOnTimeout(T value, long timeout, TimeUnit unit)
如果在给定的超时之前未以其他方式完成,此方法将使用给定的值完成此 CompletableFuture。
public CompletableFuture<T> orTimeout(long timeout, TimeUnit unit)
如果在给定的超时之前未以其他方式完成,此方法将以 TimeoutException 异常方式完成此 CompletableFuture。
改进对子类化的支持
public Executor defaultExecutor()
它返回用于未指定 Executor 的 async 方法的默认 Executor。子类可以重写此方法以返回一个 Executor,从而提供至少一个独立线程。
public <U> CompletableFuture<U> newIncompleteFuture()
返回一个新的未完成的 CompletableFuture,其类型与 CompletionStage 方法返回的类型相同。CompletableFuture class 的子类应重写此方法以返回与此 CompletableFuture 相同类的实例。默认实现返回 CompletableFuture 类的实例。
新增工厂方法
public static <U> CompletableFuture<U> completedFuture(U value)
此工厂方法返回一个已使用给定值完成的新 CompletableFuture。
public static <U> CompletionStage<U> completedStage(U value)
此工厂方法返回一个已使用给定值完成的新 CompletionStage,并且仅支持 interface CompletionStage 中存在的方法。
public static <U> CompletionStage<U> failedStage(Throwable ex)
此工厂方法返回一个已以给定异常异常方式完成的新 CompletionStage,并且仅支持 interface CompletionStage 中存在的方法。
其他功能
除了上述功能外,Java 9 对 JDK 平台进行了更多增强。其中一些列在下面。
- GC (Garbage Collector) 改进
- Stack-Walking API
- 过滤传入的序列化数据
- 弃用 Applet API
- Indify String Concatenation
- 增强 Method Handles
- Java Platform Logging API and Service
- Compact Strings
- Nashorn 的 Parser API