跳至主要內容

Java 11 新特性总结

大约 23 分钟

Java 11 新特性总结

Java 11 新特性—概述

Java 11 在 2018 年 9 月 25 日正式发布,与 Java 9 和 Java 10 这两个被称为”功能性的版本”不同,Java 11 仅将提供长期支持服务(LTS, Long-Term-Support),还将作为 Java 平台的默认支持版本,并且会提供技术支持直至 2023 年 9 月,对应的补丁和安全警告等支持将持续至 2026 年。同时,从 Java 11 开始, Oracle JDK 不再可以免费的用于商业用途,当然如果你是个人使用,或者是使用 Open JDK ,那么还是可以免费使用的。

img

JEP 181: 基于嵌套的访问控制

Java 一直以来都支持嵌套类,包括静态嵌套类(static nested classes)和非静态嵌套类(inner classes)。这些嵌套类允许我们在逻辑上更紧密地组织相关的类,提高代码的可读性和可维护性。

但是在 Java 11 之前,为了让嵌套类能够访问彼此的私有成员,编译器需要生成桥接方法(bridge methods)或合成字段(synthetic fields)。这些是一些额外的、通常是不必要的公共方法和字段。这种方式违反了封装原则也有一定的性能和安全问题。

所以,需要有一种机制来优化嵌套类之间的访问控制,同时保持代码的封装性和清晰性。于是,Java 11 引入嵌套类访问控制,该特性解决了内部类与外部类之间访问控制的问题。

主要内容有两点:

  1. 引入Nest 的概念

    nest 是一个新引入的概念。一个 nest 由一个顶层类(nest host)和它的所有内部类(nest members)组成。它允许一个 nest 中的所有类相互看到对方的私有成员(private fields, methods, and constructors)。

  2. 引入两个属性

    1. NestHost 属性标识了一个类的 nest host。
    2. NestMembers 属性列出了属于同一个 nest 的所有成员。
    3. 这些属性被添加到 class 文件格式中,允许 JVM 在类加载时确定哪些类是同一个 nest 的一部分。

新增**String **API

String 绝对是 Java 中最常用的一个类了,String 类的方法使用率也都非常的高,Java 11 为了更好地处理字符串,引入了几个新的 API。

方法名描述
isBlank()检查字符串是否为空或仅包含空白字符
lines()分割获取字符串流(Stream)
strip()去除字符串首尾的空白字符
repeat(n)复制字符串

更多阅读:Java 11 新特性—新增 String APIopen in new window

JEP 321:全新的 HTTP 客户端 API

在 Java 11 之前,Java 提供了HttpURLConnection类,但这个类的功能比较基础,但是随着时间的流逝,要求变得越来越复杂,对应用程序的要求也越来越高,导致该类越来越不方便了。在Java 9的时候,引入了一个新的HTTP客户端API(HttpClient)作为作为实验特性,到了 Java 11,则被标准化并正式成为Java标准库的一部分。

全新的 HTTP 客户端 API 具有如下几个优势:

  1. HTTP/2支持:全新的 HttpClient 支持 HTTP/2 协议的所有特性,包括同步和异步编程模型。
  2. WebSocket支持:支持WebSocket,允许建立持久的连接,并进行全双工通信。
  3. 同步和异步:提供了同步和异步的两种模式。这意味着我们既可以等待HTTP响应,也可以使用回调机制处理HTTP响应。
  4. 链式调用:新的HttpClient 允许链式调用,使得构建和发送请求变得更简单。
  5. 更好的错误处理机制:新的HttpClient 提供了更好的错误处理机制,当HTTP请求失败时,可以通过异常机制更清晰地了解到发生了什么。

下面是一个使用新的 HttpClient 示例:

HttpClient client = HttpClient.newHttpClient();

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://www.skjava.com"))
    .build();

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

System.out.println(response.statusCode());
System.out.println(response.body());

这是同步的代码,异步如下:

client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
    .thenApply(HttpResponse::body)
    .thenAccept(System.out::println)
    .join();

更多阅读:Java 11 新特性—全新的 HTTP 客户端 APIopen in new window

JEP 323:局部变量类型推断的升级

Java 10 引入局部变量类型推断open in new window,通过var关键字允许编译器推断变量的类型,这大大简化了Java代码的编写。但是在 Java 10 中我们是不能在 Lambda参数使用var,这在Java 11中得到了改进。

在Java 11中,我们可以使用var关键字来声明Lambda表达式输入参数的类型,允许在Lambda参数上使用注解或者省略类型。例如:

// Java 11之前的Lambda表达式
BinaryOperator<Integer> adder = (a, b) -> a + b;

// Java 11使用var的Lambda表达式
BinaryOperator<Integer> adder = (var a, var b) -> a + b;

也可以使用注解:

BinaryOperator<Integer> adder = (@NotNull var a, @NotNull var b) -> a + b;

更多阅读:Java 11 新特性—局部变量类型推断的升级open in new window

JEP 318:Epsilon—低开销垃圾回收器

Epsilon 垃圾回收器的目标是开发一个控制内存分配,但是不执行任何实际的垃圾回收工作。它提供一个完全消极的 GC 实现,分配有限的内存资源,最大限度的降低内存占用和内存吞吐延迟时间。

Java 版本中已经包含了一系列的高度可配置化的 GC 实现。各种不同的垃圾回收器可以面对各种情况。但是有些时候使用一种独特的实现,而不是将其堆积在其他 GC 实现上将会是事情变得更加简单。

下面是 no-op GC 的几个使用场景:

  • 性能测试:什么都不执行的 GC 非常适合用于 GC 的差异性分析。no-op (无操作)GC 可以用于过滤掉 GC 诱发的性能损耗,比如 GC 线程的调度,GC 屏障的消耗,GC 周期的不合适触发,内存位置变化等。此外有些延迟者不是由于 GC 引起的,比如 scheduling hiccups, compiler transition hiccups,所以去除 GC 引发的延迟有助于统计这些延迟。
  • 内存压力测试:在测试 Java 代码时,确定分配内存的阈值有助于设置内存压力常量值。这时 no-op 就很有用,它可以简单地接受一个分配的内存分配上限,当内存超限时就失败。例如:测试需要分配小于 1G 的内存,就使用-Xmx1g 参数来配置 no-op GC,然后当内存耗尽的时候就直接 crash。
  • VM 接口测试:以 VM 开发视角,有一个简单的 GC 实现,有助于理解 VM-GC 的最小接口实现。它也用于证明 VM-GC 接口的健全性。
  • 极度短暂 job 任务:一个短声明周期的 job 任务可能会依赖快速退出来释放资源,这个时候接收 GC 周期来清理 heap 其实是在浪费时间,因为 heap 会在退出时清理。并且 GC 周期可能会占用一会时间,因为它依赖 heap 上的数据量。 延迟改进:对那些极端延迟敏感的应用,开发者十分清楚内存占用,或者是几乎没有垃圾回收的应用,此时耗时较长的 GC 周期将会是一件坏事。
  • 吞吐改进:即便对那些无需内存分配的工作,选择一个 GC 意味着选择了一系列的 GC 屏障,所有的 OpenJDK GC 都是分代的,所以他们至少会有一个写屏障。避免这些屏障可以带来一点点的吞吐量提升。

Epsilon 垃圾回收器和其他 OpenJDK 的垃圾回收器一样,可以通过参数 -XX:+UseEpsilonGC 开启。

Epsilon 线性分配单个连续内存块。可复用现存 VM 代码中的 TLAB 部分的分配功能。非 TLAB 分配也是同一段代码,因为在此方案中,分配 TLAB 和分配大对象只有一点点的不同。Epsilon 用到的 barrier 是空的(或者说是无操作的)。因为该 GC

执行任何的 GC 周期,不用关系对象图,对象标记,对象复制等。引进一种新的 barrier-set 实现可能是该 GC 对 JVM 最大的变化。

摘自:https://pdai.tech/md/java/java8up/java11.html

ZGC:可伸缩低延迟垃圾收集器

ZGC 即 Z Garbage Collector(垃圾收集器或垃圾回收器),这应该是 Java 11 中最为瞩目的特性,没有之一。ZGC 是一个可伸缩的、低延迟的垃圾收集器,主要为了满足如下目标进行设计:

  • GC 停顿时间不超过 10ms
  • 即能处理几百 MB 的小堆,也能处理几个 TB 的大堆
  • 应用吞吐能力不会下降超过 15%(与 G1 回收算法相比)
  • 方便在此基础上引入新的 GC 特性和利用 colord
  • 针以及 Load barriers 优化奠定基础
  • 当前只支持 Linux/x64 位平台 停顿时间在 10ms 以下,10ms 其实是一个很保守的数据,即便是 10ms 这个数据,也是 GC 调优几乎达不到的极值。根据 SPECjbb 2015 的基准测试,128G 的大堆下最大停顿时间才 1.68ms,远低于 10ms,和 G1 算法相比,改进非常明显。

img

不过目前 ZGC 还处于实验阶段,目前只在 Linux/x64 上可用,如果有足够的需求,将来可能会增加对其他平台的支持。同时作为实验性功能的 ZGC 将不会出现在 JDK 构建中,除非在编译时使用 configure 参数: --with-jvm-features=zgc 显式启用。

在实验阶段,编译完成之后,已经迫不及待的想试试 ZGC,需要配置以下 JVM 参数,才能使用 ZGC,具体启动 ZGC 参数如下:

-XX:+ UnlockExperimentalVMOptions -XX:+ UseZGC -Xmx10g

其中参数: -Xmx 是 ZGC 收集器中最重要的调优选项,大大解决了程序员在 JVM 参数调优上的困扰。ZGC 是一个并发收集器,必须要设置一个最大堆的大小,应用需要多大的堆,主要有下面几个考量:

  • 对象的分配速率,要保证在 GC 的时候,堆中有足够的内存分配新对象。
  • 一般来说,给 ZGC 的内存越多越好,但是也不能浪费内存,所以要找到一个平衡。

摘自:https://pdai.tech/md/java/java8up/java11.html

JEP 335:废弃 Nashorn JavaScript 引擎

Nashorn JavaScript 引擎最初是在**JDK 8 **中引入的,用于取代 Rhino 脚本引擎,它允许在JVM上执行和调用JavaScript代码。但是随着 Java 11 的到来标志着 Nashorn JavaScript 引擎的废弃。

Java 11 废弃 Nashorn JavaScript 引擎的主要原因是因为它难以跟上现代 JavaScript 发展的步伐,同时Oracle决定将资源集中在Java核心功能上,而不是维护与Java生态系统关联不大的组件,鼓励开发者转而使用更专注的JavaScript运行时环境,如Node.js。

增加 Files API

Java 11 为 Files 类增加了两个非常有用的实用方法,进一步简化了文件读写的操作:readString()writeString()

  • Files.readString(Path path):读取一个文件的所有内容并将其返回为一个字符串。该方法非常适用于一次性读取整个文件。
  • Files.writeString(Path path, CharSequence csq, OpenOption... options):将一个字符串写入到文件中。

这两个方法使得读写文件变得更加方便。

更多阅读:Java 11 新特性—新增 Files APIopen in new window

Optional API 增强

Optional 是 Java 8 引入的,用来方便我们以一种更加优雅的方式来处理NullPointerException。Java 11 为了能够进一步方法我们使用它,新增了一个方法:

方法描述
isEmpty()判断容器是否为空,如果包含的值不存在,则返回 true。

更多阅读:Java 11 新特性—Optional API 的增强open in new window

JEP 328:飞行记录器(Flight Recorder

飞行记录器(Java Flight Recorder)是一种监控工具,它收集运行中的Java虚拟机(JVM)的详细运行时信息,这些信息可以用于诊断问题,以及分析和改进性能。飞行记录器之前是商业版 JDK 的一项分析工具,但从Java 11开始,它被贡献给了OpenJDK,成为了所有Java开发者可用的标准功能。

飞行记录器记录的主要数据源于应用程序、JVM 和 OS,这些事件信息保存在单独的事件记录文件中,故障发生后,能够从事件记录文件中提取出有用信息对故障进行分析。启用参数:

java -XX:StartFlightRecording=filename=myrecording.jfr,duration=60s MyApplication

这条命令会启动一个60秒的飞行记录,并将结果保存在myrecording.jfr文件中。

还可以使用 jcmd 工具在运行中的Java应用程序上启动和控制JFR,例如:

jcmd <PID> JFR.start
jcmd <PID> JFR.dump filename=myrecording.jfr
jcmd <PID> JFR.stop

其中 <PID> 是Java进程的进程ID。

收集到的JFR记录文件可以使用多种工具进行分析,最常用的是JDK Mission Control(JMC)。JMC提供了一个图形界面,用于查看和分析JFR产生的数据文件。

JEP 330:运行单文件源码程序

这项特性允许开发者直接运行一个包含源代码的单个Java文件,而无需事先将其编译成字节码。这个特性简化了运行简单Java程序的过程,使得快速测试和运行小段代码更加便捷。

我们可以使用 Java 命令直接运行一个单文件的Java源代码,比如我们有一个名为 HelloWorld.java 的文件,内容如下:

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

我们可以使用下面命令来执行这个 Java 文件

java HelloWorld.java

输出内容为:

Hello, World!

JEP 320:删除 Java EE 和 corba 模块

在 Java 9 和 Java 10 中 Java EE(现Jakarta EE)和CORBA模块就已经被标记为弃用了,并且在默认情况下是不可用的,在 Java 11 中,被彻底移除了。删除的模块包括:

  • Java EE模块

    • java.xml.ws (JAX-WS, SOAP Web服务)
    • java.xml.bind (JAXB, XML到Java对象的绑定)
    • java.activation (JAF, JavaBeans Activation Framework)
    • java.xml.ws.annotation (Web服务注解)
    • javax.jws, javax.jws.soap, javax.xml.soap, 等等。
  • CORBA模块

    • java.corba (包含了CORBA、IDL和IIOP相关的类和接口)

Java 11 新特性—新增 String API

String 绝对是 Java 中最常用的一个类了,String 类的方法使用率也都非常的高,Java 11 为了更好地处理字符串,引入了几个新的 API:

方法名描述
isBlank()检查字符串是否为空或仅包含空白字符
lines()分割获取字符串流(Stream)
strip()去除字符串首尾的空白字符
repeat(n)复制字符串
isBlank()

用于判断字符串是否为空或者只包含空白字符。

"".isBlank();   // true
" ".isBlank();   // true
" \n\t ".isBlank()   // true
"skjava.com".isBlank();  // false

该方法是对isEmpty()的补充,isEmpty() 只能检查字符串长度是否为0,而isBlank()则回进一步检查了字符串的内容是否也只包含空白字符。

lines()

该方法返回一个流(Stream),该流由字符串中的行组成,使用行结束符作为分隔符。

"死磕 Java\n死磕 Java 新特性\n死磕 Netty".lines().forEach(System.out::println);
// 结果......
死磕 Java
死磕 Java 新特性
死磕 Netty

该方法可以处理不同平台上的换行符,无论是\n\r还是\r\n

strip()

去除字符串首尾的空白字符。如果字符串中间有空白字符,它们不会被去掉。

System.out.println(" 死磕 Java  ".strip());
// 结果......
死磕 Java

在原来老的 Java 版本中有一个方法:trim(),该方法也可以去掉空格,但是它只能去掉除半角空格。看下面例子

// 全角
System.out.println("[" + " 死磕 Java 新特性 ".strip() + "]");   //[死磕 Java 新特性]
System.out.println("[" + " 死磕 Java 新特性 ".trim() + "]");    //[ 死磕 Java 新特性 ]

// 半角
System.out.println("[" + " 死磕 Java 新特性 ".strip() + "]");    //[死磕 Java 新特性]
System.out.println("[" + " 死磕 Java 新特性 ".trim() + "]");     //[死磕 Java 新特性]

所以:

  • trim() 只移除ASCII字符集中定义的空白字符。
  • strip() 移除所有Unicode字符集中定义的空白字符。

strip() 还有两个方法:

  • stripLeading():仅移除开头的空白字符。
  • stripTrailing():仅移除末尾的空白字符。

这里就不做介绍了。

repeat()

将字符串重复指定的次数,并将这些重复的字符串连接为一个新的字符串。

System.out.println("死磕 Java 新特性,".repeat(3));
// 结果......
死磕 Java 新特性,死磕 Java 新特性,死磕 Java 新特性,
  • 如果传入的次数为0,结果是一个空字符串。
  • 如果次数小于0,会抛出IllegalArgumentException

Java 11 新特性—新增 Files API

Java 11 对java.nio.file.Files新增了三个 API:

  • readString():读取文件内容
  • writeString():写入文件内容
  • isSameFile():比较两个路径是否指向文件系统中的同一个文件

这三个 API 简化了我们对文件的操作,使得操作文件变得更加简便了。

readString()

该方法可以一次性读取文件的全部内容,避免了以前我们使用 BufferedReader 类的繁琐过程。

    @Test
    public void readStringTest() throws IOException {
        Path  path = Paths.get("skjava.txt");
        String content = Files.readString(path);
        System.out.println(content);
    }
writeString()

该方法用于将字符串内容直接写入文件中,避免了以前我们使用 BufferedWriter 类的繁琐过程。

    @Test
    public void writeStringTest() throws IOException {
        Path  path = Paths.get("skjava.txt");
        String content = "www.skjava.com 是大明哥的个人网站,收录了目前最新最全的 Java 系列文章。【死磕 Java】是大明哥打造的进阶类 Java 系列文章,从入门、进阶、实战、源码方方面面阐述 Java 核心原理,助你提升 Java 技术一臂之力。"
        Files.writeString(path,content);
    }
isSameFile()

该方法用于比较两个路径是否指向文件系统中的同一个文件,可以用来检查符号链接或文件快捷方式是否指向同一个文件。

    @Test
    public void isSameFileTest() throws IOException {
        Path path1 = Paths.get("skjava_01.txt");
        Path path2 = Paths.get("skjava_02.txt");
        boolean result = Files.isSameFile(path1,path2);
        System.out.println(result);
    }

文件匹配返回 true,否则返回 false。

Java 11 新特性—Optional API 的增强

Optional 是在 Java 8 中引入用于处理可能为null的对象。它提供了一种更优雅的方法来减少NullPointerException的可能性。为了我们能够方便地使用 Optional,Java 11 对它进行了增强,主要是新增了一个 API:

方法描述
isEmpty()判断容器是否为空,如果包含的值不存在,则返回 true。
isEmpty()

如果 Optional 对象为空,isEmpty() 返回 true,它是 isPresent() 的对立面。

Optional<String> optional = Optional.empty();
if (optional.isEmpty()) {
    System.out.println("Optional is empty");
}

当我们需要检查 Optional 是否不包含任何值时,这个方法是非常有用的。它提供了一种更直观的方式来表达这个检查,特别是在不包含值的情况需要特别处理时。

Java 11 新特性—局部变量类型推断的增强

局部变量类型推断是 Java 10 引入的,通过使用var关键字允许编译器推断变量的类型,这大大简化了Java代码的编写。但是在 Java 10 中我们是不能在 Lambda参数使用var,这在Java 11中得到了改进。

关于局部变量类型推断参考文章:Java 10 新特性—局部变量类型推断open in new window

在 Java 11 之前,我们是不能在Lambda表达式的参数中使用var,例如:

Function<String, String> func = (var str) -> str.toUpperCase();

这种写法在 Java 11 之前是非法的,但是在 Java 11 中是合法的,在这个例子中,str 的类型被推断为String

由于可以在 Lambda 表达式中可以使用 var 了,所以我们还可以将注解与类型推断结合使用。例如:

BiFunction<String, String, String> biFunction = (@NonNull var a, @NonNull var b) -> a + b;

在这里,ab都被推断为String类型,且都使用了@NonNull注解。

Java 11 新特性—全新的 HTTP 客户端 API

Java 9 提供了新的HTTP 客户端(HttpClient)来处理HTTP调用,但是那时还处于孵化器阶段,经历了 Java 9 、Java 10 ,终于在Java11版本正式“转正”。

img

为什么要提供全新的 HTTP 客户端

Java 11 引入新的 HTTP 客户端的原因是为了解决旧的HttpURLConnection类存在的多个问题和局限性。HttpURLConnection 存在如下几个局限性:

  1. 不直观的API设计HttpURLConnection 的 API 设计过于复杂,对于新手比较难上手,例如我们必须手动处理输入和输出流,以及错误处理。
  2. 不支持 HTTP/2HttpURLConnection仅支持 HTTP/1.1,不支持 HTTP/2。HTTP/2支持多路复用、服务器推送和头部压缩等多项特性,这些都能提高网络通信的效率。
  3. 性能问题HttpURLConnection 在高并发场景下性能表现不是很好,尤其是同步阻塞的特性更是性能的瓶颈。
  4. 缺乏现代Web特性的支持:随着Web技术的发展,如 WebSocket 等技术变得越来越重要,而HttpURLConnection并不支持这些。
  5. 缺乏异步处理能力:在处理HTTP请求时,HttpURLConnection不支持异步模式。
  6. 错误处理繁琐HttpURLConnection 的错误处理比较麻烦。我们需要检查特定的HTTP错误代码,并对不同的响应代码进行不同的处理。
  7. 连接管理不足HttpURLConnection 不支持连接池、不支持自动重试。
  8. 不可变对象和线程安全HttpURLConnection不是不可变的,也不是线程安全的。

基于上述几点,Java 11 引入 HttpClient 来替代HttpURLConnection。相比HttpURLConnectionHttpClient 具有如下优势:

  1. 支持 HTTP/2HttpClient 支持 HTTP/2 协议的所有特性,包括同步和异步编程模型。
  2. 支持** WebSocket**:支持WebSocket,允许建立持久的连接,并进行全双工通信。
  3. 更简洁的APIHttpClient提供了更简洁、更易于使用的 API,我们能够非常方便地发送 HTTP 请求。
  4. 支持同步和异步:提供了同步和异步的两种模式。这意味着我们既可以等待HTTP响应,也可以使用回调机制处理HTTP响应。
  5. 链式调用:新的HttpClient 允许链式调用,使得构建和发送请求变得更简单。
  6. 更好的错误处理机制:新的HttpClient 提供了更好的错误处理机制,当HTTP请求失败时,可以通过异常机制更清晰地了解到发生了什么。

核心类介绍

HttpClient的主要类有下面三个:

  • java.net.http.HttpClient
  • java.net.http.HttpRequest
  • java.net.http.HttpResponse

HttpClient

HttpClient是用于发送请求和获取响应的客户端。它可以发送同步和异步的HTTP请求,并支持HTTP/1.1、HTTP/2 以及WebSocket。

由于HttpClient是不可变的,所以它是线程安全的,可以重用以发送多个请求。

利用HttpClient.newBuilder() 可以创建HttpClient的实例,并可以配置各种属性,如:

priority配置属性
version()指定HTTP协议的版本(HTTP/1.1或HTTP/2)
followRedirects()设置重定向策略
proxy()设置代理服务器
authenticator()设置HTTP身份验证
connectTimeout()置建立HTTP连接的超时时间
executor()设置自定义的Executor,该Executor用于异步任务
cookieHandler()设置Cookie处理器
sslParameters()设置SSL/TLS的配置
priority()设置请求的优先级

这些方法可以使用链式的方式配置在一起,一旦配置完成,调用build()方法就会创建一个新的HttpClient实例,该实例包含所有配置的设置。例如:

HttpClient client = HttpClient.newBuilder()
                .version(HttpClient.Version.HTTP_2)
                .followRedirects(HttpClient.Redirect.NEVER)
                .proxy(ProxySelector.of(new InetSocketAddress("127.0.0.1", 8808)))
                .connectTimeout(Duration.ofMinutes(1))
                .build();
HttpClient

HttpRequest

HttpRequest 表示一个HTTP请求,它也是不可变的,所以一旦创建,就不能更改它的配置和数据。使用 HttpRequest.newBuilder() 来创建 HttpRequest 实例,然后链式调用设置方法来配置请求,配置项如下:

方法名配置项
uri()设置请求的统一资源标识符 (URI)
timeout()设置请求的超时时间
header()添加单个请求头
headers()批量添加请求头
version()指定 HTTP 协议的版本
expectContinue()设置 Expect: 100-Continue 请求头
setHeader()设置特定的请求头,如认证头

HttpRequest 支持 GET、POST、DELETE 等方法,对应的方法如下:

方法名描述
GET()创建一个 GET 请求
POST(HttpRequest.BodyPublisher body)创建一个带有请求体的 POST 请求
PUT(HttpRequest.BodyPublisher body)创建一个带有请求体的 PUT 请求
DELETE()创建一个 DELETE 请求
method(String method, HttpRequest.BodyPublisher body)创建一个自定义方法的请求

下面代码是创建一个 GET 请求:

HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://www.skjava.com"))
                .version(HttpClient.Version.HTTP_2)
                .timeout(Duration.ofSeconds(10))
                .GET()
                .build();

对于需要请求体的请求,例如 POST、PUT,我们可以使用HttpRequest.BodyPublishers来提供请求体的内容。

HttpResponse

HttpResponse 表示一个 HTTP 响应,它包含HTTP响应的所有信息,包括状态码、响应头和响应体。与HttpClientHttpRequest一样,HttpResponse也是不可变的。

当我们利用 HttpClient 发送 HTTP 请求时,需要指定一个HttpResponse.BodyHandler来处理响应体。这个处理器决定了如何处理响应数据,比如将其作为String,如下:

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

HttpResponse 方法如下:

方法名描述
statusCode()获取HTTP响应的状态码
body()获取HTTP响应体
headers()获取HTTP响应头
uri()获取请求的URI
version()获取响应的HTTP协议版本
request()获取生成此响应的HttpRequest对象
previousResponse()获取重定向之前的响应,如果有的话
sslSession()获取SSL会话信息,如果在SSL连接上获得响应,则为Optional<SSLSession>
trailers()异步获取HTTP尾部头,返回CompletableFuture<HttpHeaders>

示例

HttpClient 可以使用同步发送和异步发送。

同步发送

HttpClient 提供了 send() 方法,发送请求后会一直阻塞到收到response为止。

    @Test
    public void sendTest() {
        // 构建 HttpClient 对象
        HttpClient httpClient = HttpClient.newBuilder()
                .version(HttpClient.Version.HTTP_2)     //HTTP版本为HTTP/2
                .connectTimeout(Duration.ofSeconds(10))  // 连接超时为10秒
                .build();

        // 构建 HttpRequest 对象
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://skjava.com/article/list?page=3")) // uri
                .timeout(Duration.ofSeconds(5))     // 超时时间为 5 秒
                .GET().build();

        HttpResponse<String> response = null;
        try {
            response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());

            if (response.statusCode() == 200) {
                // 请求成功
                System.out.println("Response Body: " +  response.body());
            } else {
                // 请求不成功
                System.err.println("Response Status Code: " + response.statusCode());
                System.err.println("Response Body: " + response.body());
            }
        } catch (IOException e) {
            // 捕获并处理网络I/O错误
            System.err.println("IOException occurred: " + e.getMessage());
        } catch (InterruptedException e) {
            // 捕获并处理请求过程中的中断异常
            System.err.println("InterruptedException occurred: " + e.getMessage());
            // 中断当前线程
            Thread.currentThread().interrupt();
        }
    }

首先构建 HttpClientHttpRequest 实例对象,调用 HttpClient 的 send() 方法发送 HTTP 同步请求,结果如下:

img

发送同步请求处理比较简单,我们只需要注意处理 IOExceptionInterruptedException 两个异常。

异步发送

HttpClient 提供了 sendAsync() 方法用于发送异步请求,发送请求后会立刻返回 CompletableFuture,然后我们可以使用CompletableFuture中的方法来设置异步处理器。

    @Test
    public void sendAsyncTest() {
        // 构建 HttpClient 对象
        HttpClient httpClient = HttpClient.newBuilder()
                .version(HttpClient.Version.HTTP_2)     //HTTP版本为HTTP/2
                .connectTimeout(Duration.ofSeconds(10))  // 连接超时为10秒
                .build();

        // 构建 HttpRequest 对象
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://skjava.com/article/list?page=1")) // uri
                .timeout(Duration.ofSeconds(5))     // 超时时间为 5 秒
                .GET().build();

        // 发送异步请求
        CompletableFuture<HttpResponse<String>> futureResponse = httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString());

        // 处理响应
        futureResponse.thenApply(HttpResponse::body)    // 获取响应体
                .thenAccept(System.out::println)        // 打印响应体
                .join();                // 等待所有的操作完成
    }

注意,异步请求 sendAsync() 返回的是一个 CompletableFuture,关于 可以看这篇文章:Java 8 新特性—深入理解 CompletableFutureopen in new window

上次编辑于:
贡献者: 诗人都藏在水底