Java 简明教程

Java 9 - New Features

JAVA 9(又称 jdk 1.9)是 JAVA programming language 开发的一个主要版本。其初始版本于 2017 年 9 月 21 日发布。Java 9 版本的主要目标为 −

  1. 使 JDK 和 Java Standard Edition 平台基于模块,以便可以很好地将其缩小到小型计算设备上。

  2. 提高 JDK 和 Java 实现的整体安全性。

  3. 为 JAVA SE 和 EE 平台简化 Java 代码库和大型应用程序的构建过程和维护。

  4. 设计和实现一个适用于 Java Platform 的标准模块系统,该系统可以轻松地应用于平台和 JDK。

以下是 Java 9 中支持的新功能列表:

Module System

Module System 被引入以将 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

Example - Using Module

下面的代码段定义了在应用程序根文件夹的 module-info.java 文件中声明的模块。

module com.tutorialspoint.greetings {
   requires com.tutorialspoint.util;
   requires static com.tutorialspoint.logging;
   requires transitive com.tutorialspoint.base;

   exports com.tutorialspoint.greetings.HelloWorld;
   opens com.tutorialspoint.greetings.HelloWorld;
}

这里我们声明我们的模块依赖于三个模块,并导出一个供外部世界使用的公共类,并允许反射检查特定类。默认情况下,无法通过反射访问模块的私有成员。

REPL

REPL 代表 Read Evaluate Print Loop。REPL Engine*JShell* 在 Java 9 中作为交互式控制台引入,用于在控制台运行任意的 Java 代码片段,而无需保存和 compile java code file。JShell 会读取输入的每一行代码,对其进行评估,然后打印结果,然后再次准备好接受下一组输入。

Example - Using JShell as REPL

以下代码段展示如何在 JShell 中创建 variables。分号是可选的。我们还可以在 JShell 中创建 objects。如果变量未初始化,则会对其赋予默认值,如果它是一个对象引用,则赋予 null 值。一旦创建了一个变量,就可以使用它,如最后一条语句所示,我们使用了字符串变量来打印其值。

Example

在以下示例中,我们创建了变量、评估了表达式、创建了日期对象。

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"

Improved JavaDocs

从 Java 9 起,Java 现在支持 HTML5 输出生成,并且提供了一个搜索框来生成 API 文档。

Example

在此示例中,我们正在创建一个符合 HTML5 规范的 javadoc。

考虑 C:/JAVA 文件夹中的以下代码。

Tester.java

/**
  * @author MahKumar
  * @version 0.1
  */
public class Tester {
   /**
      * Default method to be run to print
      * <p>Hello world</p>
      * @param args command line arguments
      */
   public static void main(String []args) {
      System.out.println("Hello World");
   }
}

使用 -html5 标志运行 jdk 9 的 javadoc 工具,以生成新类型的文档。

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 output1

Multirelease JAR

Java 9 中的多发行 JAR 特性增强了 JAR 格式,以便类文件的多个 Java 发行版特定版本可以在单个存档中共存。

在多发行 JAR 格式中,JAR 文件可以具有不同版本的 Java 类或资源,这些资源可以根据平台进行维护和使用。在 JAR 中,文件 MANIFEST.MF 文件在其主部分中有一个条目 Multi-Release: true。META-INF 目录还包含一个 versions 子目录,其子目录(从 Java 9 的 9 开始)存储特定于版本的类和资源文件。

使用 MANIFEST.MF,我们可以在不同的位置指定 Java 9 或更高版本的特定类,如下所示:

Java Multi-Release Jar Files Directory Structure Example

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,那么它将选择根级别类进行加载和执行,否则,将加载特定于版本的类。例如,如果在 Java 8 中使用了上述 JAR,那么将使用根级别的 Util.class。如果 Java 9 执行了相同的 JAR,那么将选择特定于 Java 9 版本的类,依此类推。通过这种方式,第三方库/框架可以在不更改针对较低版本编写的源代码的情况下支持新特性。

Collection Factory Methods Improvements

在 Java 9 中,向 List、Set 和 Map 接口添加了新的静态工厂方法来创建这些集合的不变实例。这些工厂方法主要是便利的工厂方法,以便以更简洁的方式创建集合。

Example of List Interface Factory Methods Before Java 9

在此处,我们正在创建 Java 9 之前的不可修改列表。

package com.tutorialspoint;

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]

Example of List Interface Factory Methods in Java 9

在此处,我们正在 Java 9 中创建不可修改列表。

package com.tutorialspoint;

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]

Private Interface Methods

Java 9 中引入了私有和静态私有接口方法。由于是私有方法,因此无法通过实现类或子接口访问此类方法。引入此方法是为了允许将特定方法的实现仅保留在接口中。它有助于减少重复,提高可维护性,并编写简洁的代码。

Example - Private method in Interface from Java 9

package com.tutorialspoint;

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 Improvements

在 Java 9 中,负责控制和管理操作系统进程的 Process API 已得到极大改进。ProcessHandle 类现在提供进程的本机进程 ID、启动时间、累积的 CPU 时间、参数、命令、用户、父进程和后代。ProcessHandle 类还提供用于检查进程的活跃状态和销毁进程的方法。它有 onExit 方法,CompletableFuture 类可以在进程退出时异步执行动作。

Spawning a new Process Example

在此示例中,我们为记事本创建了一个新的 Process,并使用 ProcessBuilder 启动了该 Process。使用 ProcessHandle.Info 接口,我们正在获取新生成的进程的进程信息。

package com.tutorialspoint;

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\Tutorialspoint

Stream API Improvements

在 Java 8 中引入了流来帮助开发人员从对象序列执行聚合操作。利用 Java 9,增加了几个方法以使流更加高效。

takeWhile(Predicate Interface) Method

default Stream<T> takeWhile(Predicate<? super T> predicate)

takeWhile 方法获取所有值,直到谓词返回 false。对于有序流,它返回一个由从该流中提取的最长元素前缀组成、与给定谓词相匹配的流。

dropWhile(Predicate Interface)

default Stream<T> dropWhile(Predicate<? super T> predicate)

dropWhile 方法丢弃所有值,直到谓词返回 true。对于有序流,它返回一个由从该流中删除与给定谓词相匹配的最长元素前缀后的剩余元素组成流。

iterate Method

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 和避免对流进行 null 检查。此方法返回一个包含单个元素(如果非空)的顺序流,否则返回一个空流。

Try with Resources Improvements

在 Java 9 之前,资源应在如下面的给定示例中所示 try 语句之前或内部声明。在此示例中,我们将使用 BufferedReader 作为资源来读取字符串,然后关闭 BufferedReader。

Java 9 onwards

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

Enhanced @Deprecated Annotation

@Deprecated 注释在 java 5 版本中引入。用 @Deprecated 注释的程序元素表示不应使用该元素,原因如下 -

  1. 其用法可能导致错误。

  2. 它可能与未来版本不兼容。

  3. 它可能在未来版本中被删除。

  4. 一个更好且高效的替代方案已经取代了它。

每当使用已过期的元素时,编译器都会生成警告。使用 Java 9,会在 @Deprecated 注释中进行两项新增强。

  1. forRemoval - 表示已注释的元素是否会在未来版本中移除。默认值为 false。

  2. since - 返回已注释的元素已过期的版本。默认值为一个空字符串。

Deprecated with since

以下 Java 9 中 Boolean 类 javadoc 的示例说明了如何在 @Deprecated 注释中使用 since 属性。

boolean

Deprecated with forRemoval

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

system

Inner Class Diamond Operator

在 Java 9 中,菱形运算符也可以与匿名类一起使用以简化代码并提高可读性。

Example

在以下示例中,我们为一个抽象类 Handler 创建了匿名类,该类接受一个泛型参数,但在创建匿名类时不带对象类型,因为不需要传递类型参数。编译器会自行推断类型。

public class Tester {
   public static void main(String[] args) {
      // create an Anonymous class to handle 1
	  // Here we do not need to pass Type arguments in diamond operator
	  // as Java 9 compiler can infer the type automatically
      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

Multiresolution Image API

多分辨率图像 API 在 Java 9 中引入。该 API 支持具有不同分辨率变体的多个图像。该 API 允许将一组具有不同分辨率的图像用作单个多分辨率图像。

考虑以下图像。

mini logo
logo
large logo

这些是具有不同大小的三张徽标图像。

现在为了操作这三个图像,可以使用 Java 9 开始的多分辨率图像 API 作为单个 API,以获取所有变体或待显示的特定变体。

// read all images into one multiresolution image
MultiResolutionImage multiResolutionImage =
   new BaseMultiResolutionImage(images.toArray(new Image[0]));

这里 MultiResolutionImage 和 BaseMultiResolutionImage 类是 java.awt.image 包的一部分。

以下是多分辨率图像的主要操作。

  1. Image getResolutionVariant(double destImageWidth, double destImageHeight) − 获取特定图像,该图像作为指示大小的逻辑图像的最佳变体。

  2. List&lt;Image&gt; getResolutionVariants() − 获取所有分辨率变体的可读列表。

Example - Get All variants

在该示例中,我们加载了三个图像并将它们存储在 MultiResolutionImage 中。然后使用 getResolutionVariants() 方法,正在检查这个多分辨率图像中的所有可用图像变体并打印它。

package com.tutorialspoint;

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 {

	  // prepare a list of urls of all images
      List<String> imgUrls = List.of("http://www.tutorialspoint.com/java9/images/logo.png",
         "http://www.tutorialspoint.com/java9/images/mini_logo.png",
         "http://www.tutorialspoint.com/java9/images/large_logo.png");

      // create a list of Image object
      List<Image> images = new ArrayList<Image>();

      // Create image objects using image urls
      for (String url : imgUrls) {
         images.add(ImageIO.read(new URL(url)));
      }

      // read all images into one multiresolution image
      MultiResolutionImage multiResolutionImage =
         new BaseMultiResolutionImage(images.toArray(new Image[0]));

      // get all variants of images
      List<Image> variants = multiResolutionImage.getResolutionVariants();


      System.out.println("Total number of images: " + variants.size());

      // print all the images
      for (Image img : variants) {
         System.out.println(img);
      }
   }
}

Output

让我们编译并运行上述程序,这将生成以下结果 −

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 Enhancement

Java 8 中引入了 CompletableFuture 类,用于表示可以显式设置其值和状态的 Future。它可用作 java.util.concurrent.CompletionStage。它支持在 future 完成时触发的依赖函数和操作。在 java 9 CompletableFuture 中进一步增强了 API。以下是针对 API 做出的相关更改。

  1. 支持延迟和超时。

  2. Improved support for subclassing.

  3. New factory methods added.

Support for delays and timeouts

public CompletableFuture<T> completeOnTimeout(T value, long timeout, TimeUnit unit)

如果在给定超时之前未完成,则此方法使用给定值完成此 CompletableFuture。

public CompletableFuture<T> orTimeout(long timeout, TimeUnit unit)

如果在给定超时之前尚未完成,则此方法使用 TimeoutException 异常完成此 CompletableFuture。

Improved support for subclassing

public Executor defaultExecutor()

它返回用于没有指定 Executor 的异步方法的默认 Executor。此方法可以在子类中被重写,以至少返回一个用于提供一个独立线程的 Executor。

public <U> CompletableFuture<U> newIncompleteFuture()

返回由 CompletionStage 方法返回的类型的新的未完成 CompletableFuture。CompletableFuture 类的子类应重写此方法,以返回与此 CompletableFuture 相同类的实例。默认实现返回 CompletableFuture 类的实例。

New factory Methods

public static <U> CompletableFuture<U> completedFuture(U value)

此工厂方法返回一个新的 CompletableFuture,该 CompletableFuture 已使用给定值完成。

public static <U> CompletionStage<U> completedStage(U value)

此工厂方法返回一个新的 CompletionStage,该 CompletionStage 已使用给定值完成,并且仅支持接口 CompletionStage 中存在的方法。

public static <U> CompletionStage<U> failedStage(Throwable ex)

此工厂方法返回一个新的 CompletionStage,该 CompletionStage 已使用给定异常完成,并且仅支持接口 CompletionStage 中存在的方法。

Miscellaneous features

除了上述特性外,通过 Java 9,JDK 平台还进行了许多增强。其中一些列在下面。

  1. GC (Garbage Collector) Improvements

  2. Stack-Walking API

  3. Filter Incoming Serialization Data

  4. Deprecate the Applet API

  5. Indify String Concatenation

  6. Enhanced Method Handles

  7. Java 平台日志 API 和服务

  8. Compact Strings

  9. Parser API for Nashorn