blog/source/_posts/nf-java9.md
2021-05-31 11:39:14 +08:00

9.0 KiB
Raw Blame History

title tags categories keywords date
Java 9的新特性
JVM
Java
Java 9
Optional
JPMS
新特性
JVM
Java
Java,Java 9,Optional,JPMS,模块,新特性 2021-05-20 15:15:29

Java 9在Java 8的基础上做了许多重大的改进尤其是引入了模块系统这直接导致使用Java 8的程序在Java 9中可能无法通过编译。除此之外Java 9还对Java 8中引入的功能做了一些增补和改进。

本系列的文章有:

  • {% 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语法规则中资源变量必须在资源操作语句中声明例如以下示例。

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那么就无需重新声明一个变量而直接使用了所以以上示例可以改写为以下形式。

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声明一个模块,例如以下示例。

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,选择需要导出的类。
  • exports ... to ...,选择需要导出的类,并将其导出给特定包访问。
  • requires,引用其他的模块。需要注意的是,java.base会被默认加入引用,不需要显式书写。每个模块仅会被引入一次,并且只能访问已经导出的类。requires关键字还可以搭配其他的关键字来实现更加精细的控制。
    • requires transitive,依赖传递,这表示任何依赖于本模块的的应用,也可以查看和使用声明为依赖传递的包。
    • requires static,声明静态依赖,如果指定的依赖项在模块路径上可见,那么当前模块就可以使用它,如果不可见,那也不会发生错误。
  • uses,指定当前模块需要使用的服务。服务是实现了uses指令指定的接口或者继承了uses指令指定的抽象类的对象。
  • provides ... with ...,使指定的模块成为服务的提供者。其中provides部分指定模块的uses关键字列出接口和抽象类,with部分则指定实现接口或者扩展抽象类的服务提供类的名称。
  • opens将模块中的包设为公开的因为在默认情况下JPMS中的包都是私有的。
  • opens ... to ...,将模块中的包指定开放给特定的包,仅允许特定的包有全部访问权。
  • open module,将整个模块都开放出来。

被依赖模块可以通过命令jar --create --file 目标jar文件 -C 已编译目录 要打包内容来完成打包。依赖其他模块的模块可以通过命令javac -p 依赖模块目录 -d 输出目录 源文件集来完成编译,并使用命令jar --create -f 目标jar文件 -p 依赖模块目录 --main-class 主类 -C 已编译目录 要打包内容。在运行最终的模块时需要指定所有模块的位置,例如java -p 主模块:依赖模块 -m 主类

因为JPMS的引入所以使用了JPMS的应用与没有使用JPMS的第三方库之间还存在着一定的兼容性问题并且JPMS在使用中需要注意以下问题。

  • 所有使用module-info的文件仅适用于在模块路径上使用模块化的jar。
  • 模块的版本是不能够被处理的,这也就是说相同的模块名称是不能够被加载两次的。
  • 两个模块可能包含不相同的包。
  • 在编译和运行的时候,模块之间不能有循环依赖出现。
  • 非公共字段和方法将不能通过反射访问。
  • 在模块化的应用中使用没有模块化的第三方库需要使用其jar包名称作为其模块名称。

兼容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>类进行了扩展,增加了延迟和超时等功能,并添加了新的工厂方法。