Java 9 新特性有哪些?怎么用这些新功能?

文章导读
Previous Quiz Next Java 9(也称为 JDK 1.9)是 Java 编程语言开发的一个主要版本。其初始版本于 2017 年 9 月 21 日发布。Java 9 发布的主要目标是 −
📋 目录
  1. 模块系统
  2. REPL
  3. 改进的 JavaDocs
  4. 多发布 JAR
  5. 集合工厂方法改进
  6. 私有接口方法
  7. Process API 改进
  8. Stream API 改进
  9. Try with Resources 改进
  10. 增强的 @Deprecated 注解
A A

Java 9 - 新特性



Previous
Quiz
Next

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 文档页面,您将看到以下输出。

javadoc output in java 9

多发布 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 类

Boolean Class javadoc

带有 forRemoval 的已废弃

以下 Java 9 中 System 类的 javadoc 示例说明了在 @Deprecated 注解上使用 forRemoval 属性的用法。

System 类

System Class javadoc

内部类 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 允许将一组不同分辨率的图像用作单个多分辨率图像。

考虑以下图像。

mini logo.png logo.png large logo.png

这些是三个不同尺寸的 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