Java9新特性及代码示例

  |   0 评论   |   0 浏览

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 个主要类HttpClientHttpRequestHttpResponse.

要发出请求,就像获取客户端、构建请求并发送它一样简单,示例:

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方法。此外,它还添加了两个重载方法,即ofNullableiterate方法。

新方法takeWhiledropWhile允许开发者基于谓词获取流的一部分

  • 在有序流上,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注解将具有两个属性,即forRemovalsince.

  • 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.jsJavascript 文件,以及生成 javadoc 时生成的索引。生成的 HTML5 API 页面上有一个搜索框。

请注意,默认情况下会添加搜索选项,但可以使用参数关闭:-noindex

11. 其他功能

Java 9 中还有其他功能,我在此处列出以供快速参考。我们将在接下来的帖子中讨论所有这些功能。

  • 反应式流 API
  • GC(垃圾收集器)改进
  • 过滤传入的序列化数据
  • 弃用 Applet API
  • 指示字符串连接
  • 增强的方法句柄
  • 紧凑的字符串
  • Nashorn 解析器 API

转自https://zhuanlan.zhihu.com/p/480250560


标题:Java9新特性及代码示例
作者:michael
地址:https://blog.junxworks.cn/articles/2022/12/02/1669970999841.html