Java 教程

Java 控制语句

面向对象编程

Java 内置类

Java 文件处理

Java 错误和异常

Java 多线程

Java 同步

Java 网络

Java 集合

Java 接口

Java 数据结构

Java 集合算法

高级 Java

Java 杂项

Java API 和框架

Java 类参考

Java 有用资源

Java 9 - 新特性



JAVA 9(也称为 jdk 1.9)是JAVA 编程语言开发的一个主要版本。其初始版本于 2017 年 9 月 21 日发布。Java 9 版本的主要目标是:

  • 使 JDK 和 Java 标准版平台基于模块化,这意味着它可以缩减到小型计算设备。

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

  • 使 Java 代码库和大型应用程序的构建过程和维护对于 JAVA SE 和 EE 平台来说更容易。

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

以下是 Java 9 支持的新特性列表

模块系统

模块系统旨在将 Java 代码中的模块化提升到一个新的水平。模块是代码和数据的自描述集合。模块可以包含包,以及特定于特定功能的配置。模块提供了对自身内容的更好的访问控制。从 Java 9 开始,Java 库被分成多个模块,可以使用以下命令查看。

C:\Users\Mahesh>java --list-modules
[email protected]
[email protected]
[email protected]
[email protected]
...
[email protected]
[email protected]

示例 - 使用模块

以下代码片段定义了一个在应用程序根文件夹中的 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 代表 读取-求值-打印-循环。Java 9 中引入了一个 REPL 引擎JShell,它是一个交互式控制台,用于在控制台中运行任意 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 {
   /**
      * 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");
   }
}

运行 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 版本的类文件可以共存于单个归档文件中。

在多版本 Jar 格式中,一个 jar 文件可以包含不同版本的 Java 类或资源,可以根据平台进行维护和使用。在 JAR 中,文件 MANIFEST.MF 文件在其主部分中有一个条目 Multi-Release: true。META-INF 目录还包含一个 versions 子目录,其子目录(从 Java 9 开始以 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 之前不可修改的列表。

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]

Java 9 中 List 接口工厂方法的示例

在这里,我们正在创建 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]

私有接口方法

Java 9 引入了私有和静态私有接口方法。作为私有方法,此类方法无法通过实现类或子接口访问。引入这些方法是为了允许封装,其中某些方法的实现将仅保留在接口中。这有助于减少重复性,提高可维护性和编写整洁的代码。

示例 - 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

类似地,我们可以拥有私有静态方法,该方法可以从静态和非静态方法中调用。

进程 API 改进

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

生成新进程示例

在此示例中,我们为记事本创建了一个新进程,并使用 ProcessBuilder 启动它。使用 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 改进

Stream 在 Java 8 中引入,以帮助开发人员从对象序列执行聚合操作。在 Java 9 中,添加了一些方法来改进 Stream。

takeWhile(Predicate 接口) 方法

语法

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

takeWhile 方法获取所有值,直到谓词返回 false。在有序流的情况下,它返回一个流,该流包含从该流中获取的与给定谓词匹配的最长前缀元素。

dropWhile(Predicate 接口)

语法

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

dropWhile 方法丢弃开头所有值,直到谓词返回 true。在有序流的情况下,它返回一个流,该流包含在丢弃与给定谓词匹配的最长前缀元素后该流的剩余元素。

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 方法是为了防止 NullPointerExceptions 并避免 Stream 的空检查。此方法返回一个包含单个元素的顺序 Stream(如果非空),否则返回一个空 Stream。

带资源的 try 改进

在 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

内部类菱形运算符

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

示例

在下面的示例中,我们为抽象类 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

多分辨率图像 API

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

考虑以下图像。

mini logo.png logo.png large logo.png

这些是三个不同尺寸的徽标图像。

现在,为了使用这三个图像,从 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 包的一部分。

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

  • Image getResolutionVariant(double destImageWidth, double destImageHeight) - 获取最适合在指定尺寸下表示此逻辑图像的特定图像。

  • List<Image> getResolutionVariants() - 获取所有分辨率变体的可读列表。

示例 - 获取所有变体

在此示例中,我们加载了三个图像并将它们存储在 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("https://tutorialspoint.com/java9/images/logo.png",
         "https://tutorialspoint.com/java9/images/mini_logo.png",
         "https://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);
      }     
   }  
}

输出

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

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 类是在 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 的异步方法的默认 Executor。可以在子类中重写此方法以返回 Executor,以提供至少一个独立线程。

public <U> CompletableFuture<U> newIncompleteFuture()

返回 CompletionStage 方法要返回的类型的新的不完整 CompletableFuture。CompletableFuture 类的子类应重写此方法以返回与该 CompletableFuture 相同类的实例。默认实现返回 CompletableFuture 类的实例。

新的工厂方法

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

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

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

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

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

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

其他功能

除了提到的功能外,Java 9 还对 JDK 平台进行了大量增强。其中一些列在下面。

  • GC(垃圾收集器)改进
  • 堆栈遍历 API
  • 过滤传入的序列化数据
  • 弃用 Applet API
  • 优化字符串连接
  • 增强的 Method Handles
  • Java 平台日志记录 API 和服务
  • 紧凑字符串
  • Nashorn 的解析器 API
广告