# Java 9 特性和增强特性

> 原文： [https://howtodoinjava.com/java9/java9-new-features-enhancements/](https://howtodoinjava.com/java9/java9-new-features-enhancements/)

Java 9 带来了许多新的增强特性，它们将在很大程度上影响您的编程风格和习惯。 最大的变化是 Java 的模块化。 这是 [Java 8](//howtodoinjava.com/java-8-tutorial/) 中的 [Lambdas](//howtodoinjava.com/java8/complete-lambda-expressions-tutorial-in-java/) 之后的又一重大变化。 在本文中，我列出了将作为 JDK 9 版本的一部分的更改。

```java
What is new in Java 9

Java platform module system
Interface Private Methods
HTTP 2 Client
JShell - REPL Tool
Platform and JVM Logging
Process API Updates
Collection API Updates
Stream API Improvements
Multi-Release JAR Files
@Deprecated Tag Changes
Stack Walking
Java Docs Updates
Miscellaneous Other Features
```

## Java 平台模块系统

JPMS（Java 平台模块系统）是新 Java 9 版本的核心亮点。 它也被称为 [Jigshaw 项目](https://openjdk.java.net/projects/jigsaw/)。 模块是新的结构，就像我们已经有了包一样。 使用新的模块化编程开发的应用程序可以看作是交互模块的集合，这些模块之间具有明确定义的边界和依赖性。

JPMS 包括为编写模块化应用程序以及模块化 JDK 源代码提供支持。 JDK 9 随附约 92 个模块（GA 版本中可能会进行更改）。 Java 9 模块系统具有一个“`java.base`”模块。 它被称为基本模块。 这是一个独立模块，不依赖于任何其他模块。 默认情况下，所有其他模块都依赖于“`java.base`”。

在 Java 模块化编程中：

1.  模块通常只是一个 jar 文件，其根目录具有`module-info.class`文件。
2.  要使用模块，请将 jar 文件而不是`classpath`包含在`modulepath`中。 添加到类路径的模块化 jar 文件是普通的 jar 文件，`module-info.class`文件将被忽略。

典型的`module-info.java`类如下所示：

```java
module helloworld {
    exports com.howtodoinjava.demo;
}

module test {
    requires helloworld;
}

```

> 阅读更多： [Java 9 模块教程](//howtodoinjava.com/java9/java-9-modules-tutorial/)

## 接口私有方法

Java 8 允许您在接口中编写默认方法，这是广受赞赏的特性。 因此，在此之后，接口仅缺少一些东西，只有非私有方法是其中之一。 从 Java 9 开始，您可以在接口中包含私有方法。

这些私有方法将改善接口内部的代码可重用性。 举例来说，如果需要两个默认方法来共享代码，则可以使用私有接口方法来共享代码，但不必将该私有方法暴露给实现类。

在接口中使用私有方法有四个规则：

1.  接口私有方法不能是抽象的。
2.  私有方法只能在接口内部使用。
3.  私有静态方法可以在其他静态和非静态接口方法中使用。
4.  私有非静态方法不能在私有静态方法内部使用。

在接口中使用私有方法的示例： 

```java
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();
    }
}

```

> [Java 9 – 接口](//howtodoinjava.com/java9/java9-private-interface-methods/)中的私有方法

## HTTP/2 客户端

HTTP/1.1 客户端于 1997 年发布。此后发生了很多变化。 因此，对于 Java 9，引入了新的 API，该 API 使用起来更加简洁明了，并且还增加了对 HTTP/2 的支持。 新 API 使用 3 个主要类，即`HttpClient`，`HttpRequest`和`HttpResponse`。

要发出请求，就像获取客户，建立请求并发送它一样简单，如下所示。

```java
HttpClient httpClient = HttpClient.newHttpClient(); 
HttpRequest httpRequest = HttpRequest.newBuilder().uri(new URI("//howtodoinjava.com/")).GET().build(); 
HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandler.asString()); 
System.out.println( httpResponse.body() ); 

```

上面的代码看起来更简洁易读。

新 API 还使用`httpClient.sendAsync()`方法支持异步 HTTP 请求。 它返回`CompletableFuture`对象，该对象可用于确定请求是否已完成。 请求完成后，它还使您可以访问`HttpResponse`。 最好的部分是，如果您愿意，甚至可以在请求完成之前取消它。 例如：

```java
if(httpResponse.isDone()) {
    System.out.println(httpResponse.get().statusCode());
    System.out.println(httpResponse.get().body());
} else {
    httpResponse.cancel(true);
}

```

## JShell – REPL 工具

**JShell** 是 **JDK 9** 发行版（[JEP 222](https://openjdk.java.net/jeps/222)）附带的新命令行交互工具，用于求值用 Java 编写的声明，语句和表达式。 JShell 允许我们执行 Java 代码段并获得即时结果，而无需创建解决方案或项目。

Jshell 非常类似于 linux OS 中的命令窗口。 区别在于 JShell 是 Java 特定的。 除了执行简单的代码片段外，它还有许多其他特性。 例如

*   在单独的窗口中启动内置代码编辑器
*   在单独的窗口中启动您选择的代码编辑器
*   在这些外部编辑器中发生“保存”操作时执行代码
*   从文件系统加载预编写的类

> [Java 9 JShell 教程](//howtodoinjava.com/java9/complete-jshell-tutorial-examples/)

## 平台和 JVM 日志记录

JDK 9 通过新的日志记录 API 改进了平台类（JDK 类）和 JVM 组件中的日志记录。 它使您可以指定所选的日志记录框架（例如 [Log4J2](//howtodoinjava.com/log4j2/)）作为日志记录后端，用于记录来自 JDK 类的消息。 关于此 API，您应该了解几件事：

1.  该 API 是由 JDK 中的类而非应用程序类使用的。
2.  对于您的应用程序代码，您将像以前一样继续使用其他日志记录 API。
3.  该 API 不允许您以编程方式配置记录器。

该 API 包含以下内容：

*   服务接口`java.lang.System.LoggerFinder`，它是抽象的静态类
*   接口`java.lang.System.Logger`，它提供日志记录 API
*   `java.lang.System`类中的重载方法`getLogger()`，它返回记录器实例。

JDK 9 还添加了一个新的命令行选项`-Xlog`，使您可以单点访问从 JVM 所有类记录的所有消息。 以下是使用`-Xlog`选项的语法：

```java
-Xlog[:][:[

<output>][:[<decorators>][:<output-options>]]]</output-options></decorators></output> 
```

所有选项都是可选的。 如果缺少`-Xlog`中的前一部分，则必须对该部分使用冒号。 例如，`-Xlog::stderr`表示所有部件均为默认设置，输出设置为`stderr`。

我将在单独的文章中深入讨论该主题。

## 进程 API 更新

在 Java 5 之前，产生新进程的唯一方法是使用`Runtime.getRuntime().exec()`方法。 然后在 Java 5 中，引入了`ProcessBuilder` API，该 API 支持一种更干净的方式产生新进程。 现在，Java 9 添加了一种获取有关当前进程和任何衍生进程的信息的新方法。

要获取任何进程的信息，现在应该使用`java.lang.ProcessHandle.Info`接口。 此接口在获取大量信息方面很有用，例如

*   用于启动过程的命令
*   命令的参数
*   进程开始的瞬间
*   它和创建它的用户所花费的总时间

```java
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`实例。 其余的一切都如上所述。

```java
String javaPrompt = ProcessUtils.getJavaCmd().getAbsolutePath();
ProcessBuilder processBuilder = new ProcessBuilder(javaPrompt, "-version");
Process process = processBuilder.inheritIO().start();
ProcessHandle processHandle = process.toHandle();

```

也可以使用`ProcessHandle.allProcesses()`获取系统中所有可用进程的`ProcessHandle`流。

要获取所有子进程的列表（直接和深层次的子进程），请使用`children()`和`descendants()`方法。

```java
Stream<ProcessHandle> children    = ProcessHandle.current().children();
Stream<ProcessHandle> descendants = ProcessHandle.current().descendants();

```

## 集合 API 更新

从 Java 9 开始，您可以使用新的工厂方法创建不可变集合，例如不可变列表，不可变集合和不可变映射。 例如

```java
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"));
    }
}

```

> [Java 9 集合 API 的改进](//howtodoinjava.com/java9/create-immutable-collections-factory-methods/)

## 流 API 的改进

Java 9 引入了两种与流进行交互的新方法，即`takeWhile`/`dropWhile`方法。 此外，它还添加了两个重载方法，即`ofNullable`和`iterate`方法。

新方法`takeWhile`和`dropWhile`允许您基于谓词获取流的一部分。

1.  在有序流上，`takeWhile`返回流的“最长前缀”，其元素匹配给定谓词。 `dropWhile`返回匹配给定谓词的“最长前缀”之后的其余项。
2.  在无序流上，`takeWhile`返回流的子集，从流的开头开始，其元素匹配给定谓词（但不是全部）。 `dropWhile`返回不匹配给定谓词的剩余的流元素。

同样，在 Java 8 之前，流中不能具有`null`值。 会导致`NullPointerException`。 从 Java 9 开始，`Stream.ofNullable()`方法使您可以创建一个单元素流，如果不包含`null`，则包装一个值，否则将为空流。 从技术上讲，`Stream.ofNullable()`与流条件上下文中的空条件检查非常相似。

> [Java 9 流 API 的改进](//howtodoinjava.com/java9/stream-api-improvements/)

## 多版本 JAR 文件

此增强与将应用程序类打包到 jar 文件中的方式有​​关。 以前，您必须将所有类打包到一个 jar 文件中，并放到另一个要使用它的应用程序的类路径中。

现在，通过使用多发行版特性，一个 jar 可以包含一个类的不同版本 – 与不同的 JDK 版本兼容。 有关类的不同版本以及通过加载的类应选择哪个类的 JDK 版本的信息存储在`MANIFEST.MF`文件中。 在这种情况下，`MANIFEST.MF`文件在其主要部分中包含条目`Multi-Release: true`。

此外，`META-INF`包含一个`versions`子目录，该目录的整数子目录（从 Java 9 开始）存储特定于版本的类和资源文件。 例如：

```java
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 文件：

```java
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 彼此不兼容的大型应用程序中经常出现的依赖地狱，这看起来确实是很有前途的一步。 此特性可能对解决这些情况有很大帮助。

## `@Deprecated`的标签更改

从 Java 9 开始，[`@Deprecated`](http://download.java.net/java/jdk9/docs/api/java/lang/Deprecated.html)注解将具有两个属性，即`forRemoval`和`since`。

1.  *`forRemoval`* – 指示带注释的元素是否在将来的版本中会被删除。
2.  *`since`* – 它返回已弃用带注释元素的版本。

强烈建议在文档中使用`@deprecated` javadoc 标记说明不赞成使用程序元素的原因。 该文档还应该建议并链接到建议的替代 API（如果适用）。 替换 API 的语义通常会有所不同，因此也应讨论此类问题。

## 栈的遍历

栈是后进先出（LIFO）数据结构。 在 JVM 级别，栈存储帧。 每次调用方法时，都会创建一个新框架并将其推入栈的顶部。 方法调用完成后，框架将被破坏（从栈中弹出）。 栈上的每个帧都包含其自己的局部变量数组，其自己的操作数栈，返回值以及对当前方法类的运行时常量池的引用。

在给定线程中，任何时候只有一帧处于活动状态。 活动帧称为当前帧，其方法称为当前方法。（[阅读更多](https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5.2)）

直到 Java 8，`StackTraceElement`代表一个栈帧。 要获得完整的栈，您必须使用`Thread.getStackTrace()`和`Throwable.getStackTrace()`。 它返回了`StackTraceElement`数组，您可以对其进行迭代以获取所需的信息。

在 Java 9 中，引入了新类`StackWalker`。 该类使用当前线程的栈帧顺序流提供简单有效的栈遍历。 `StackWalker`类非常有效，因为它懒惰地求值栈帧。

```java
// Prints the details of all stack frames of the current thread

StackWalker.getInstance().forEach(System.out::println);

```

您可以使用此信息流执行其他许多操作，我们将在其他一些专用文章中介绍这些内容。

## Java 文档更新

Java 9 增强了`javadoc`工具以生成 HTML5 标记。 当前，它以 HTML 4.01 生成页面。

为了生成 HTML5 Javadoc，需要在命令行参数中加入参数`-html5`。 要在命令行上生成文档，可以运行：

```java
javadoc [options] [packagenames] [sourcefiles] [@files]
```

使用 HTML5 可以带来更轻松的 HTML5 结构的好处。 它还实现了 [WAI-ARIA 标准](https://www.w3.org/WAI/intro/aria)的可访问性。 这样做的目的是使身体或视觉障碍的人更容易使用屏幕阅读器等工具访问 javadocs 页面。

[JEP 225](https://openjdk.java.net/jeps/225) 提供了在 Javadoc 中搜索程序元素以及标记的单词和短语的特性。

以下内容将被索引并可以搜索：

*   声明的模块名称
*   配套
*   类型和成员
*   方法参数类型的简单名称

这是通过新的`search.js` Javascript 文件以及在生成 Javadoc 时生成的索引在客户端实现的。 在生成的 HTML5 API 页面上有一个搜索框。

请注意，默认情况下将添加“搜索”选项，但可以使用参数`-noindex`将其关闭。

## 其他特性

Java 9 中还有其他特性，我在这里列出以供快速参考。 我们将在以后的文章中讨论所有这些特性。

*   反应式流 API
*   GC（垃圾收集器）改进
*   筛选传入的序列化数据
*   弃用 Applet API
*   字符串化连接
*   增强的方法句柄
*   精简字符串
*   Nashorn 的解析器 API

Java 9 计划于 2017/09/21 发行。 对于最新更改，请遵循此[链接](https://openjdk.java.net/projects/jdk9/)。

学习愉快！