Java9新特性详解

位置:首页>文章>详情   分类: Java教程 > 编程技术   阅读(179)   2024-05-27 06:20:37

对许多人来说,Java 9 似乎是一个维护版本,它推动了 Jigsaw 项目,而 Jigsaw 在 Java 8 中无法实现。但是随着 JDK 中的新模块系统以及与之相关的许多内部变化,Java 9 也带来了一个开发人员工具箱中的许多很酷的新东西。以下是亮点:

  • JShell – Java 现在有一个内置的 shell;
  • 新的流程 API – 提供了许多在以前版本中默认缺少的流程处理功能;
  • 将 G1 作为默认垃圾收集器——在 Java 8 中这是并行 GC;
  • 全新的 HTTP/2 客户端——由 jdk.incubator.httpclient.HttpClient 类和 jdk.incubator.httpclient 包的类提供;
  • 新的堆栈遍历 API – 提供用于分析和使用 Java 堆栈跟踪的标准 API;
  • 新的反应式编程 API – 在 JDK java.util.concurrent.Flow 类中提供标准机制;
  • 语言增强——这些是一些小的语言改进,最重要的是在接口中拥有私有方法的能力(例如,Java 8 中引入的默认方法)。早前为该语言引入 var 关键字的提议已针对 Java 10 进行了调整(尽管最初计划在 9 中进行)。 Coin 项目带来了进一步的改进(包括一系列小的语言更改);
  • 其他 – 改进的 Javadoc、集合工厂方法(例如 Set.of(…)、Map.of(…)、List.of(…))、流 API 改进、私有接口方法、多版本 JAR 和许多本文未涵盖的其他内容(包括并发性和安全性增强)(好奇的读者可以在此处查看与 Java 9 相关的 JEP:http://openjdk.java.net/projects/jdk9/ ).

JDK 9 中没有实现的另一件事是打包 JMH 工具并提供默认的 JMH 微基准测试以供应用程序使用——工作仍在进行中……

打破巨石

假设我们有一个 Java 9 之前的应用程序,它可以将文本导出为不同的格式——文本、pdf 或 word。应用程序结构如下:

exporter.IExporter 接口为不同的导出器提供通用接口:

public interface IExporter {
	public void export(String text, ByteArrayOutputStream baos);	
}

三个具体的导出器实现如下:

public class PdfExporter implements IExporter {
	public void export(String text, ByteArrayOutputStream baos) {
		Document document = null;
		try {
			document = new Document();
			PdfWriter.getInstance(document, baos);
			document.open();
			document.add(new Paragraph(text));
			document.close();
		} catch (DocumentException e) {
			if (document != null) {
				document.close();
			}
		}
	}
}
public class WordExporter implements IExporter {
	public void export(String text, ByteArrayOutputStream baos) {
		XWPFDocument document = null;
		try {
			document = new XWPFDocument();
			XWPFParagraph paragraph = document.createParagraph();
			XWPFRun run = paragraph.createRun();
			run.setText(text);
			document.write(baos);
		} catch (IOException e) {
			try {
				if (document != null) {
					document.close();
				}
			} catch (IOException e1) {
			}
			// some logging or proper error handling ...
		}
	}
}
public class TextExporter implements IExporter {
	public void export(String text, ByteArrayOutputStream baos) {
		try {
			baos.write(text.getBytes());
		} catch (IOException e) {
			// some logging or proper error handling ...
		}
	}
}

exporter.Main 类是应用程序的入口点,如果可能具有以下实现,则仅使用 PdfExporter 类:

public static void main(String[] args) throws IOException {
		PdfExporter exporter = new PdfExporter();
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		exporter.export("sample text", baos);
		FileOutputStream fos = new FileOutputStream("example.pdf");
		fos.write(baos.toByteArray());
		fos.close();
}

对于 PDF 和 Word 导出器,我们需要以下依赖项(我们使用 Maven 构建应用程序):

<dependencies>
		<dependency>
			<groupid>org.apache.poi</groupid>
			<artifactid>poi</artifactid>
			<version>3.16</version>
		</dependency>
		<dependency>
			<groupid>org.apache.poi</groupid>
			<artifactid>poi-ooxml</artifactid>
			<version>3.16</version>
		</dependency>
		<dependency>
			<groupid>com.lowagie</groupid>
			<artifactid>itext</artifactid>
			<version>2.1.7</version>
		</dependency>		
</dependencies>

因此,第一件事是为您的应用程序定义模块以及您愿意使用的 JDK 模块。您的初始应用程序在类路径中包含所有 JDK 类,但您可以在 Java 9 中将其限制为您需要的 JDK 模块。对于我们的简单应用程序,我们仅使用来自 JDK 的 java.io 类,它们是每个 Java 应用程序所需的 java.base 模块的一部分。我们的简单应用程序不需要其他 JDK 模块,因此我们可以为我们的应用程序使用非常简约的 JRE 版本。

对于实际应用我们可以很容易的推导出四个逻辑模块:

  • exporter——该模块为所有导出器(exporter.IExporter 类)提供通用 API;
  • exporter.word – 此模块提供 Word 导出器;
  • exporter.pdf – 此模块提供 PDF 导出器;
  • exporter.text – 此模块提供文本导出器;
  • exporter.demo – 此模块包含 Main 类并提供我们应用程序的入口点。

接下来是为我们定义的每个模块添加模块元数据。为此,我们为每个模块定义了一个 module-info.java 文件,如下所示:

每个模块的模块元数据具有以下内容(请注意三个具体的导出器如何依赖于 API 模块,并且我们不需要 java.base 模块,因为它是隐式需要的):

module exporter {
	exports exporter;
}
module exporter.pdf {
	requires exporter;
	requires itext;
	exports exporter.pdf;
}
module exporter.text {
	requires exporter;
	exports exporter.text;
}
module exporter.word {
	requires exporter;
}
module exporter.demo {
	requires exporter;
	requires poi.ooxml;
	requires poi.ooxml.schemas;
	exports exporter.word;
}

我们仍然需要以某种方式从模块中引用第三方库(IText 和 Apache POI)。如果这些库是模块化版本,我们可以简单地下载并在相应的应用程序模块中使用它们。然而,我们不能做出这种假设,因此我们需要一种方法将它们作为“原样”的依赖项进行引用。出于迁移的目的,Java 平台提供了一种将第三方库用作 Java 模块的机制。如果它们在我们应用程序的类路径中,它们将包含在全局“未命名”模块中,并且库中的所有公共类都将对我们的应用程序模块可见。另一方面,由于为指定所有应用程序模块的模块化应用程序引入了新的模块路径,第三方库也可以使用 JAR 文件的名称(不带 .jar 扩展名)从模块路径中引用——这些就是所谓的自动模块。我们更喜欢第二种选择,因为它是一种更“模块化”的迁移方法。现在最后一件事是构建和运行我们的模块化应用程序。事实上,我们对模块结构有不同的策略:比如在同一个项目中(我们有一个)多个模块,一个项目一个模块甚至嵌套模块结构。我们也可以将 module-info.java 文件放在模块中我们想要的任何位置,只要指定为 javac 编译过程的一部分(因此位于模块目录的根目录中)。

在撰写本文时,Maven 仍然不支持构建模块化应用程序,而且 Java IDE(例如 Eclipse、IntelliJ 和 NetBeans)对 Jigsaw 模块有部分和实验性支持。出于这个原因,我们将演示如何从命令行构建我们的模块化应用程序。为此,我们将首先在原始项目根目录中创建 modules 目录。并按如下方式构建五个模块(请注意,Java 9 还为 javac 提供了一个 –module-source-path 参数,以便可以立即编译模块,但我们不会将其用于演示如何单独构建模块的目的)——我们需要在项目的根目录中,并且在 PATH 上有最新的 JDK 9 bin 目录:

mkdir modules\exporter modules\exporter.pdf modules\exporter.word modules\exporter.text modules\exporter.demo
javac -d modules\exporter src\exporter\module-info.java src\exporter\IExporter.java
javac --module-path modules -d modules\exporter.text src\exporter\text\module-info.java src\exporter\text\TextExporter.java
javac --module-path modules;C:\Users\Martin\.m2\repository\com\lowagie\itext\2.1.7 -d modules\exporter.pdf src\exporter\pdf\module-info.java src\exporter\pdf\PdfExporter.java
javac -cp C:\Users\Martin\.m2\repository\org\apache\poi\poi\3.16\poi-3.16.jar --module-path modules;C:\Users\Martin\.m2\repository\org\apache\poi\poi-ooxml\3.16;C:\Users\Martin\.m2\repository\org\apache\poi\poi-ooxml-schemas\3.16 -d modules\exporter.word src\exporter\word\module-info.java src\exporter\word\WordExporter.java
javac --module-path modules -d modules\exporter.demo src\exporter\demo\module-info.java src\exporter\demo\Main.java

这里有两个要点。首先确保从第三方依赖项的 Maven 目录中排除源 jars(如果有的话),因为这可能会使编译器误以为您在同一目录(例如 poi-3.16 和 poi-3.16-sources.jar)中有重复的模块 jar。罐)。其次,对于自动模块,您可能会从不同的库中导出相同的包(也称为拆分包),这在 Java 9 中是不允许的。Apache POI 就是这种情况:poiexporter.word 模块所需的 strong> 和 poi-ooxml 库导出 org.apache.poi 包,如果两者都导致编译错误其中包括作为自动模块。为了在我们的案例中解决这个问题,我们在类路径中包含 poi 库,它构成了从自动模块中可见的所谓的全局未命名模块。现在,为了运行演示应用程序,您需要运行以下命令,该命令结合了上面用于编译的模块路径/类路径,并指向 Main 类作为我们应用程序的主要入口点:

javac --module-path modules -d modules\exporter.demo src\exporter\demo\module-info.java src\exporter\demo\Main.java

因此,您应该在当前目录中生成了 example.pdf 文件。

玩转 Java Shell

JSHell REPL 位于 JDK9 的 bin 目录中,您只需导航到该目录并键入 jshell 即可运行它。您可以(重新)定义和检查变量、导入、方法和类型。您还可以保存当前进度并在 JSHell 中再次加载它。对导入、类型和方法也有很棒的自动完成支持。为了说明 JSHell 的基础知识,请考虑在 JShell 中尝试以下命令集:

2 + 3
public int sum(int a, int b) { return a + b; }
import java.util.logging
import java.util.logging.*
Logger x = null;
class z {}
/help
/imports
/vars
/methods
/env
/types
/list
/list -all
/save script.jsh
/open script.jsh
/exit

控制外部流程

在 Java 9 之前,您可以使用两种方法来创建新进程——使用 Runtime.getRuntime().exec() 方法或 java.lang.ProcessBuilder 类,如下所示:

Process p = Runtime.getRuntime().exec("cmd /c notepad");
ProcessBuilder pb = new ProcessBuilder("cmd", "/c", “notepad");
Process p = pb.start();</pre>

然而,这在管理创建的进程或检查主机操作系统中的其他外部进程方面非常有限。出于这个原因,JDK 9 一方面向 java.lang.Process 类引入了许多新方法,而 java.lang.ProcessHandle 提供了一个新实用程序,它另一方面,允许以类似流的方式操作外部进程。以下是新流程 API 的一些自我描述示例:

LOGGER.info("PID: " + process.pid());
		LOGGER.info("Number of children: " + process.children().count());
		LOGGER.info("Number of descendants: " + process.descendants().count());
		
		ProcessHandle.Info info = process.info();
		LOGGER.info("Process command: " + info.command());
		LOGGER.info("Info: " + info.toString());
		
//		ProcessHandle handle = process.toHandle();
		
		CompletableFuture<process> exitedFuture = process.onExit();
		exitedFuture.whenComplete((p, e) -> { LOGGER.info("Process exited ... ");});
		exitedFuture.get();</process></pre>
<pre class="brush: java;">ProcessHandle[] processes = ProcessHandle.allProcesses().filter((pHandle) -> { return pHandle.info().toString().contains(name); }).toArray(ProcessHandle[] :: new);
		for(ProcessHandle process : processes) {
			LOGGER.info("Process details: " + process.info().toString());
		}
		
		return processes;

使用 G1 提升性能

G1 垃圾收集器并不是新的,而是在 JDK 7 中引入的。G1 的具体之处在于它在堆中按区域工作,而不是按代工作,这是一种更细粒度的垃圾清理方法。它旨在在遵守一组约束条件的同时尽可能多地清理垃圾。它是一个低暂停收集器,以高吞吐量换取低暂停时间。在 JDK 9 中,它成为默认的垃圾收集器,取代了吞吐量更高的并行 GC。此更改背后的假设是,较短的 GC 暂停时间带来的用户体验改善优于收集器的高吞吐量(并行 GC 就是这种情况)。该假设是否正确留给应用程序来决定——如果有人不愿意冒转移到 G1 的风险,仍然可以使用 -XX:-UseParallelGC 为 JVM 指定并行 GC

为 HTTP 2.0 做好准备

jdk.incubator.http.HttpClient 类提供了一个新的 HTTP 2.0 客户端,该类仍处于孵化阶段(意味着可能会出现更多变化)。

遍历堆栈跟踪

java.lang.StackWalker 类提供了一个新的堆栈跟踪检查 API。它可用于使用类似流的操作以细粒度的方式过滤和操作堆栈跟踪信息。

包含响应式编程

想要提供适合反应式编程范例的发布-订阅机制的应用程序必须提供 java.util.concurrent.Flow 类的实现。 JDK 提供的一种标准发布器实现了Flow.Publisher 接口,由java.util.concurrent.SubmissionPublisher 类提供。

标签2: Java教程
地址:https://www.cundage.com/article/jcg-java-9-glance.html

相关阅读

Java HashSet 教程展示了如何使用 Java HashSet 集合。 Java哈希集 HashSet 是一个不包含重复元素的集合。此类为基本操作(添加、删除、包含和大小)提供恒定时间性...
SpringApplicationBuilder 教程展示了如何使用 SpringApplicationBuilder 创建一个简单的 Spring Boot 应用程序。 春天 是用于创建企业应...
通道是继 buffers 之后 java.nio 的第二个主要新增内容,我们在之前的教程中已经详细了解了这一点。通道提供与 I/O 服务的直接连接。 通道是一种在字节缓冲区和通道另一端的实体(通...
课程大纲 Elasticsearch 是一个基于 Lucene 的搜索引擎。它提供了一个分布式的、支持多租户的全文搜索引擎,带有 HTTP Web 界面和无模式的 JSON 文档。 Elasti...
解析器是强大的工具,使用 ANTLR 可以编写可用于多种不同语言的各种解析器。 在这个完整的教程中,我们将: 解释基础:什么是解析器,它可以用来做什么 查看如何设置 ANTLR 以便在 Java...
Java 是用于开发各种桌面应用程序、Web 应用程序和移动应用程序的最流行的编程语言之一。以下文章将帮助您快速熟悉 Java 语言,并迈向 API 和云开发等更复杂的概念。 1. Java语言...
Java中的继承是指子类继承或获取父类的所有非私有属性和行为的能力。继承是面向对象编程的四大支柱之一,用于提高层次结构中类之间的代码可重用性。 在本教程中,我们将了解 Java 支持的继承类型,...
Java Message Service 是一种支持正式通信的 API,称为 网络上计算机之间的消息传递。 JMS 为支持 Java 程序的标准消息协议和消息服务提供了一个通用接口。 JMS 提...
之前,我介绍了spring 3 + hibernate 集成 示例和struts 2 hello world 示例。在本教程中,我将讨论在将 spring 框架与 struts 与 hibern...
Java 项目中的一项常见任务是将日期格式化或解析为字符串,反之亦然。解析日期意味着你有一个代表日期的字符串,例如“2017-08-3”,你想把它转换成一个代表 Java 中日期的对象,例如Ja...