Java 11 新特性总结
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 ,那么还是可以免费使用的。
JEP 181: 基于嵌套的访问控制
Java 一直以来都支持嵌套类,包括静态嵌套类(static nested classes)和非静态嵌套类(inner classes)。这些嵌套类允许我们在逻辑上更紧密地组织相关的类,提高代码的可读性和可维护性。
但是在 Java 11 之前,为了让嵌套类能够访问彼此的私有成员,编译器需要生成桥接方法(bridge methods)或合成字段(synthetic fields)。这些是一些额外的、通常是不必要的公共方法和字段。这种方式违反了封装原则也有一定的性能和安全问题。
所以,需要有一种机制来优化嵌套类之间的访问控制,同时保持代码的封装性和清晰性。于是,Java 11 引入嵌套类访问控制,该特性解决了内部类与外部类之间访问控制的问题。
主要内容有两点:
引入Nest 的概念
nest
是一个新引入的概念。一个nest
由一个顶层类(nest host)和它的所有内部类(nest members)组成。它允许一个 nest 中的所有类相互看到对方的私有成员(private fields, methods, and constructors)。引入两个属性
NestHost
属性标识了一个类的 nest host。NestMembers
属性列出了属于同一个 nest 的所有成员。- 这些属性被添加到 class 文件格式中,允许 JVM 在类加载时确定哪些类是同一个 nest 的一部分。
新增**String **API
String 绝对是 Java 中最常用的一个类了,String 类的方法使用率也都非常的高,Java 11 为了更好地处理字符串,引入了几个新的 API。
方法名 | 描述 |
---|---|
isBlank() | 检查字符串是否为空或仅包含空白字符 |
lines() | 分割获取字符串流(Stream) |
strip() | 去除字符串首尾的空白字符 |
repeat(n) | 复制字符串 |
JEP 321:全新的 HTTP 客户端 API
在 Java 11 之前,Java 提供了HttpURLConnection
类,但这个类的功能比较基础,但是随着时间的流逝,要求变得越来越复杂,对应用程序的要求也越来越高,导致该类越来越不方便了。在Java 9的时候,引入了一个新的HTTP客户端API(HttpClient)作为作为实验特性,到了 Java 11,则被标准化并正式成为Java标准库的一部分。
全新的 HTTP 客户端 API 具有如下几个优势:
- HTTP/2支持:全新的 HttpClient 支持 HTTP/2 协议的所有特性,包括同步和异步编程模型。
- WebSocket支持:支持WebSocket,允许建立持久的连接,并进行全双工通信。
- 同步和异步:提供了同步和异步的两种模式。这意味着我们既可以等待HTTP响应,也可以使用回调机制处理HTTP响应。
- 链式调用:新的HttpClient 允许链式调用,使得构建和发送请求变得更简单。
- 更好的错误处理机制:新的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();
JEP 323:局部变量类型推断的升级
Java 10 引入局部变量类型推断,通过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;
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 算法相比,改进非常明显。
不过目前 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)
:将一个字符串写入到文件中。
这两个方法使得读写文件变得更加方便。
Optional API 增强
Optional 是 Java 8 引入的,用来方便我们以一种更加优雅的方式来处理NullPointerException
。Java 11 为了能够进一步方法我们使用它,新增了一个方法:
方法 | 描述 |
---|---|
isEmpty() | 判断容器是否为空,如果包含的值不存在,则返回 true。 |
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 新特性—局部变量类型推断
在 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;
在这里,a
和b
都被推断为String
类型,且都使用了@NonNull
注解。
Java 11 新特性—全新的 HTTP 客户端 API
Java 9 提供了新的HTTP 客户端(HttpClient
)来处理HTTP调用,但是那时还处于孵化器阶段,经历了 Java 9 、Java 10 ,终于在Java11版本正式“转正”。
为什么要提供全新的 HTTP 客户端
Java 11 引入新的 HTTP 客户端的原因是为了解决旧的HttpURLConnection
类存在的多个问题和局限性。HttpURLConnection
存在如下几个局限性:
- 不直观的API设计:
HttpURLConnection
的 API 设计过于复杂,对于新手比较难上手,例如我们必须手动处理输入和输出流,以及错误处理。 - 不支持 HTTP/2:
HttpURLConnection
仅支持 HTTP/1.1,不支持 HTTP/2。HTTP/2支持多路复用、服务器推送和头部压缩等多项特性,这些都能提高网络通信的效率。 - 性能问题:
HttpURLConnection
在高并发场景下性能表现不是很好,尤其是同步阻塞的特性更是性能的瓶颈。 - 缺乏现代Web特性的支持:随着Web技术的发展,如 WebSocket 等技术变得越来越重要,而
HttpURLConnection
并不支持这些。 - 缺乏异步处理能力:在处理HTTP请求时,
HttpURLConnection
不支持异步模式。 - 错误处理繁琐:
HttpURLConnection
的错误处理比较麻烦。我们需要检查特定的HTTP错误代码,并对不同的响应代码进行不同的处理。 - 连接管理不足:
HttpURLConnection
不支持连接池、不支持自动重试。 - 不可变对象和线程安全:
HttpURLConnection
不是不可变的,也不是线程安全的。
基于上述几点,Java 11 引入 HttpClient
来替代HttpURLConnection
。相比HttpURLConnection
,HttpClient
具有如下优势:
- 支持 HTTP/2:
HttpClient
支持 HTTP/2 协议的所有特性,包括同步和异步编程模型。 - 支持** WebSocket**:支持WebSocket,允许建立持久的连接,并进行全双工通信。
- 更简洁的API:
HttpClient
提供了更简洁、更易于使用的 API,我们能够非常方便地发送 HTTP 请求。 - 支持同步和异步:提供了同步和异步的两种模式。这意味着我们既可以等待HTTP响应,也可以使用回调机制处理HTTP响应。
- 链式调用:新的
HttpClient
允许链式调用,使得构建和发送请求变得更简单。 - 更好的错误处理机制:新的
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响应的所有信息,包括状态码、响应头和响应体。与HttpClient
和HttpRequest
一样,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();
}
}
首先构建 HttpClient
和 HttpRequest
实例对象,调用 HttpClient 的 send()
方法发送 HTTP 同步请求,结果如下:
发送同步请求处理比较简单,我们只需要注意处理 IOException
和 InterruptedException
两个异常。
异步发送
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 新特性—深入理解 CompletableFuture