Java9新特性及代码示例
Java 9带来了许多新的增强功能,这些增强功能将在很大程度上影响你的编程风格和习惯。最大的变化是Java的模块化。这是继Java 8中的Lambdas之后的另一个重大变化。在本文中,我将列出Java 9版本的一部分更新内容。
本文主要内容:
- Java模块化
- 接口私有方法
- HTTP/2客户端
- JShell – REPL 工具
- 平台和JVM日志记录
- Process API更新
- Collection(集合) API更新
- Stream(流) API改进
- 多版本Jar文件
- @Deprecated 注解更改
- Java 文档更新
- 其他功能
1. Java 模块化
JPMS(Java Platform Module System)是Java 9发行版的核心亮点。它也被称为Jigshaw项目。模块是新的结构,就像我们已经有包一样。使用新的模块化编程开发的应用程序可以看作是交互模块的集合,这些模块之间具有明确定义的边界和依赖关系。
JPMS包括为编写模块化应用程序提供支持,以及将JDK源代码模块化。JDK 9 附带了大约 92 个模块(在 GA 版本中可以进行更改)。Java 9 Module System有一个 "java.base" 模块。它被称为基本模块。它是一个独立的模块,不依赖于任何其他模块。默认情况下,所有其他模块都依赖于"java.base"。
在java模块化编程中:
- 一个模块通常只是一个 jar 文件,在根目录下有一个文件
module-info.class
。 - 要使用模块,请将 jar 文件包含到
modulepath
而不是classpath
. 添加到类路径的模块化 jar 文件是普通的 jar 文件,module-info.class
文件将被忽略。
典型的module-info.java
类如下所示:
module helloworld {
exports cn.dayangshuo.demo;
}
module test {
requires helloworld;
}
2. 接口私有方法
Java 8 允许在接口中编写默认方法,这是一个广受欢迎的功能。从 Java 9 开始,您可以在接口中包含私有方法。
这些私有方法将提高接口内部的代码可重用性。例如,如果两个默认方法需要共享代码,私有接口方法将允许它们这样做,但不会将该私有方法暴露给它的实现类。
在接口中使用私有方法有四个规则:
- 私有接口方法不能是抽象的。
- 私有方法只能在接口内部使用。
- 私有静态方法可以在其他静态和非静态接口方法中使用。
- 私有非静态方法不能在私有静态方法中使用。
示例:
public interface CustomCalculator {
default int addEvenNumbers(int... nums) {
return add(n -> n % 2 == 0, nums);
}
default int addOddNumbers(int... nums) {
return add(n -> n % 2 != 0, nums);
}
private int add(IntPredicate predicate, int... nums) {
return IntStream.of(nums)
.filter(predicate)
.sum();
}
}
3. HTTP/2客户端
HTTP/1.1 客户端于 1997 年发布。此后发生了很多变化。因此,Java 9 引入了新的 API,它使用起来更干净、更清晰,并且还增加了对 HTTP/2 的支持。新 API 使用 3 个主要类HttpClient
,HttpRequest
和HttpResponse
.
要发出请求,就像获取客户端、构建请求并发送它一样简单,示例:
HttpClient httpClient = HttpClient.newHttpClient();
HttpRequest httpRequest = HttpRequest.newBuilder().uri(new URI("https://baidu.com/")).GET().build();
HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandler.asString());
System.out.println(httpResponse.body());
上面的代码看起来更清晰易读。
httpClient.sendAsync()
API 还支持使用方法的异步 HTTP 请求。它返回CompletableFuture
可用于确定请求是否已完成。它还可以在HttpResponse
请求完成后访问。如果你愿意,甚至可以在请求完成之前取消它。
示例:
if(httpResponse.isDone()) {
System.out.println(httpResponse.get().statusCode());
System.out.println(httpResponse.get().body());
} else {
httpResponse.cancel(true);
}
4. JShell – REPL 工具
JShell是JDK 9发行版 [ JEP 222 ]附带的新命令行交互式工具,用于评估用 Java 编写的声明、语句和表达式。JShell 允许我们执行 Java 代码片段并立即获得结果,而无需创建解决项目。
Jshell 很像我们在 linux 操作系统中的命令窗口。不同之处在于 JShell 是特定于 Java 的。除了执行简单的代码片段之外,它还有许多其他功能,例如:
- 在单独的窗口中启动内置代码编辑器
- 在单独的窗口中启动你选择的代码编辑器
- 在这些外部编辑器中发生保存操作时执行代码
- 从文件系统加载预先编写的类
5. 平台和 JVM 日志记录
JDK 9 通过新的日志记录 API 改进了平台类(JDK 类)和 JVM 组件中的日志记录。它允许开发者指定自己选择的日志记录框架(例如Log4J2)作为日志记录工具,用于记录来自 JDK 类的消息。
关于这个 API,你应该知道几件事:
- API 旨在供 JDK 中的类使用,而不是由应用程序类使用。
- 对于应用程序代码,开发者将像以前一样继续使用其他日志记录 API。
- API 不允许开发者以编程方式配置记录器。
API 由以下部分组成:
- 服务接口,
java.lang.System.LoggerFinder
是一个抽象静态类 java.lang.System.Logger
提供日志记录 API 的接口getLogger()
类中的一个重载方法java.lang.System
,它返回一个记录器实例。
JDK 9 还添加了一个新的命令行选项 ,-Xlog
它使开发者可以单点访问从 JVM 的所有类记录的所有消息。以下是使用该-Xlog
选项的语法:
-Xlog[:][:[][:[][:]]]
所有选项都是可选的。如果缺少前面的部分,-Xlog
则必须为该部分使用冒号。例如,-Xlog::stderr
表示所有部分都是默认的,输出设置为stderr
.
6. Process API更新
在 Java 5 之前,生成新进程的唯一方法是使用该Runtime.getRuntime().exec()
方法。然后在 Java 5 中,ProcessBuilder
引入了 API,它支持一种更简洁的方式来生成新进程。现在,Java 9 添加了一种获取有关当前进程和任何衍生进程的信息的新方法。
要获取任何进程的信息,现在您应该使用java.lang.ProcessHandle.Info
接口。此界面可用于获取大量信息,例如:
- 用于启动进程的命令
- 命令的参数
- 进程开始的时刻
- 它和创建它的用户花费的总时间
ProcessHandle processHandle = ProcessHandle.current();
ProcessHandle.Info processInfo = processHandle.info();
System.out.println(processHandle.getPid());
System.out.println(processInfo.arguments().isPresent());
System.out.println(pprocessInfo.command().isPresent());
System.out.println(processInfo.command().get().contains("java"));
System.out.println(processInfo.startInstant().isPresent());
要获取新衍生进程的信息,请使用process.toHandle()
方法获取ProcessHandle
实例。其余一切如上。
String javaPrompt = ProcessUtils.getJavaCmd().getAbsolutePath();
ProcessBuilder processBuilder = new ProcessBuilder(javaPrompt, "-version");
Process process = processBuilder.inheritIO().start();
ProcessHandle processHandle = process.toHandle();
也用于ProcessHandle.allProcesses()
获取系统中所有可用进程的 ProcessHandle 流。
要获取所有子进程的列表(直接以及 n 级深度),请使用children()
和descendants()
方法。
Stream<ProcessHandle> children = ProcessHandle.current().children();
Stream<ProcessHandle> descendants = ProcessHandle.current().descendants();
6. Collection(集合) API更新
从 Java 9 开始,您可以使用新的工厂方法创建不可变集合,例如不可变list、不可变set和不可变map。例如:
import java.util.List;
public class ImmutableCollections {
public static void main(String[] args) {
List<String> namesList = List.of("Lokesh", "Amit", "John");
Set<String> namesSet = Set.of("Lokesh", "Amit", "John");
Map<String, String> namesMap = Map.ofEntries(
Map.entry("1", "Lokesh"),
Map.entry("2", "Amit"),
Map.entry("3", "Brian"));
}
}
7. Stream(流) API改进
Java 9 引入了两种与 Streams 交互的新方法,即takeWhile
/dropWhile
方法。此外,它还添加了两个重载方法,即ofNullable
和iterate
方法。
新方法takeWhile
。dropWhile
允许开发者基于谓词获取流的一部分
- 在有序流上,
takeWhile
返回从流中获取的与给定谓词匹配的元素的“最长前缀”,从流的开头开始。dropWhile
返回与 不匹配的剩余项目takeWhile
。 - 在无序流上,
takeWhile
返回与给定谓词(但不是全部)匹配的流元素的子集,从流的开头开始。dropWhile
在删除与给定谓词匹配的元素子集后返回剩余的流元素。
List<String> alphabets = List.of("a", "b", "c", "d", "e", "f", "g", "h", "i");
List<String> subset1 = alphabets
.stream()
.takeWhile(s -> !s.equals("d"))
.collect(Collectors.toList());
//打印出:[a, b, c]
System.out.println(subset1);
List<String> alphabets = List.of("a", "b", "c", "d", "e", "f", "g", "h", "i");
List<String> subset2 = alphabets
.stream()
.dropWhile(s -> !s.equals("d"))
.collect(Collectors.toList());
//打印出:[d, e, f, g, h, i]
System.out.println(subset2);
同样,在 Java 8 之前,流中不能有null值。它会导致NullPointerException
. 从 Java 9 开始,Stream.ofNullable()
方法允许您创建一个单元素流,该流包装一个不为null的值,否则为空流。从技术上讲,Stream.ofNullable()
在流 API 的上下文中,与空条件检查非常相似。
8. 多版本Jar文件
此增强与如何将应用程序类打包到 jar 文件中有关。以前,开发者必须将所有类打包到一个 jar 文件中,然后放入希望使用它的另一个应用程序的类路径中。
使用多版本特性,现在一个 jar 可以包含一个类的不同版本——兼容不同的 JDK 版本。关于一个类的不同版本,以及加载的类应该选择哪个类的JDK版本的信息存储在MANIFEST.MF
文件中。在这种情况下,文件在其主要部分中MANIFEST.MF
包含该条目Multi-Release: true
此外,META-INF 包含一个版本子目录,其以整数命名的子目录——从 9 开始(对于 Java 9)——存储特定于版本的类和资源文件。例如
JAR content root
A.class
B.class
C.class
D.class
META-INF
MANIFEST.MF
versions
9
A.class
B.class
假设在 JDK 10 A.class中更新为利用一些 Java 10 新特性,那么这个 Jar 文件可以像这样更新:
JAR content root
A.class
B.class
C.class
D.class
META-INF
MANIFEST.MF
versions
9
A.class
B.class
10
A.class
它看起来非常有希望解决在大型应用程序中经常看到的依赖地狱,其中不同版本的 jar 彼此不兼容。此功能可以为解决这些情况提供很大帮助。
9. @Deprecated 注解更改
从 Java 9 开始,@Deprecated注解将具有两个属性,即forRemoval
和since
.
- forRemoval – 指示带注释的元素是否会在未来版本中被删除。
- since - 它返回注释元素被弃用的版本。
强烈建议使用 @deprecated javadoc 标记在文档中解释弃用程序元素的原因。如果适用,文档还应建议并链接到推荐的替代 API。替换 API 通常具有微妙不同的语义,因此也应讨论此类问题。
10. Java 文档更新
Java 9 增强了javadoc
生成 HTML5 标记的工具。它目前以 HTML 4.01 生成页面。
为了生成 HTML5 Javadoc,参数-html5
需要放在命令行参数中。要在命令行上生成文档,你将运行:
javadoc [选项] [包名] [源文件] [@files]
使用 HTML5 可以带来更简单的 HTML5 结构的好处。它还实现了可访问性的WAI-ARIA 标准。这旨在使身体或视觉障碍的人更容易使用屏幕阅读器等工具访问 javadocs 页面。
JEP 225提供了在 javadoc 中搜索程序元素和标记的单词和短语的能力。
以下内容将被索引和搜索:
- 模块的声明名称
- 套餐
- 类型和成员
- 方法参数类型的简单名称
这是在客户端实现的,带有一个新的search.js
Javascript 文件,以及生成 javadoc 时生成的索引。生成的 HTML5 API 页面上有一个搜索框。
请注意,默认情况下会添加搜索选项,但可以使用参数关闭:-noindex
。
11. 其他功能
Java 9 中还有其他功能,我在此处列出以供快速参考。我们将在接下来的帖子中讨论所有这些功能。
- 反应式流 API
- GC(垃圾收集器)改进
- 过滤传入的序列化数据
- 弃用 Applet API
- 指示字符串连接
- 增强的方法句柄
- 紧凑的字符串
- Nashorn 解析器 API