JDK 11引入了多项新特性,旨在提升Java语言的性能、易用性和安全性。这些特性包括局部变量类型推断、HTTP客户端API改进、移除Java EE和CORBA模块、新的飞行记录器API以及并发改进等。JDK11新特性学习涵盖了从语言层面到运行时层面的各个方面,提供了更现代化的解决方案。开发者可以通过学习这些新特性来优化和改进现有的Java应用程序。
引入与简述JDK 11是Java开发平台的标准版本之一,它于2018年9月发布,是Java 11版本的一部分。JDK 11是长期支持版本,意味着它将获得更长时间的支持和维护,直到2026年9月。在这一版本中,Oracle公司引入了多项重要的新特性,旨在改进Java语言的性能、易用性和安全性。JDK 11的新特性覆盖了从语言层面到运行时层面的各个方面,包括局部变量类型推断、HTTP客户端API的改进、移除Java EE和CORBA模块、新的飞行记录器API以及并发改进等。
JDK11新特性概述
- 局部变量类型推断:允许使用
var
关键字来声明局部变量,可以简化代码。 - HTTP客户端API:引入了一个新的标准HTTP客户端API,用以替代旧的API,使得HTTP客户端操作更加简单。
- 移除Java EE和CORBA模块:Java EE模块被移除,改为引入新的Jakarta EE,而CORBA模块也被删除。
- 新的飞行记录器API:提供了一个新API来记录JVM的运行时状态,便于进行诊断和分析。
- 并发改进与ZGC和ShenandoahGC:引入了ZGC和Shenandoah GC,这两大垃圾回收器为大型应用提供了更好的性能和响应性。
局部变量类型推断是JDK 11中的一项重要特性,它允许开发者使用var
关键字来声明局部变量,从而简化代码编写过程。var
关键字主要用于声明局部变量时,编译器能够根据赋值时的具体类型推断出变量的类型。这种方式可以减少代码冗余,提高代码的可读性和简洁性。
var关键字的使用场景
var
关键字适用于任何局部变量声明,只要编译器能够根据赋值表达式推断出变量的类型。例如,当声明一个局部变量并立即赋值时,可以使用var
关键字来代替具体的类型。这种用法尤其适用于那些类型明确的简单声明,如int
、String
或自定义类类型。
示例代码演示
下面通过一个简单的示例来展示var
关键字的使用。
public class VarExample {
public static void main(String[] args) {
// 使用 var 关键字声明局部变量
var number = 10; // 编译器推断类型为 int
var text = "Hello, World!"; // 编译器推断类型为 String
var list = List.of(1, 2, 3); // 编译器推断类型为 List<Integer>
// 输出变量值
System.out.println("Number: " + number);
System.out.println("Text: " + text);
System.out.println("List: " + list);
}
}
该示例展示了var
关键字在不同数据类型中的应用,编译器会根据赋值表达式推断出变量的实际类型。此外,还可以使用var
关键字声明复杂的变量,如列表、集合等。
实际应用示例
在实际开发中,var
关键字的使用可以简化代码,特别是在处理多层嵌套类型时。例如,处理JSON解析或API响应时,通常需要定义多个层次的类型。使用var
关键字可以简化代码,提高可读性。
public class VarInComplexExample {
public static void main(String[] args) {
// 复杂类型的示例,例如解析JSON
var jsonObject = new JSONObject();
jsonObject.put("name", "John");
jsonObject.put("age", 30);
jsonObject.put("salary", 5000);
var name = jsonObject.getString("name"); // 编译器推断为 String
var age = jsonObject.getInt("age"); // 编译器推断为 int
var salary = jsonObject.getInt("salary"); // 编译器推断为 int
System.out.println("Name: " + name);
System.out.println("Age: " + age);
System.out.println("Salary: " + salary);
}
}
``
在这一例子中,`jsonObject`的类型是`JSONObject`,而`name`、`age`和`salary`的类型分别由`getString`和`getInt`方法的返回值类型决定。使用`var`关键字可以避免显式声明这些中间变量的具体类型,从而使代码更加简洁和易读。
## HTTP客户端API
JDK 11引入了一个新的标准HTTP客户端API,旨在提供一个更加现代化和高效的HTTP客户端解决方案。这一新API取代了之前的`HttpURLConnection`,并提供了对HTTP/2协议的支持,从而提高了性能和并发能力。新API使用起来更加简洁,能够更好地处理流式数据,支持推送和多路复用等功能。
### HTTP客户端API的介绍
新的HTTP客户端API是JDK 11中的一个核心改进。它提供了一个更加现代化的、面向流的API,用于执行HTTP请求和处理响应。新API的核心接口是`HttpClient`,它提供了一系列方法来执行GET、POST、PUT、DELETE等HTTP请求。`HttpClient`还支持同步和异步请求执行,以及连接池等高级功能。
新API的设计理念是基于流式处理,这意味着请求和响应都是以流的形式来处理的。这一设计使得新API能够更好地处理大文件传输、流式数据处理等场景。此外,新API还提供了对HTTP/2协议的支持,这使得它在并发和性能方面有了显著的提升。
### 实战演示:发送HTTP请求
下面通过一个实战演示来展示如何使用新的HTTP客户端API发送HTTP请求。我们将通过发送一个GET请求到一个示例URL,并输出响应内容。
```java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpRequest.BodyPublishers;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class HttpClientExample {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建一个HttpClient实例
HttpClient client = HttpClient.newHttpClient();
// 构建HTTP请求
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/data"))
.build();
// 发送同步请求
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println("Response: " + response.body());
// 发送异步请求
CompletableFuture<HttpResponse<String>> future = client.sendAsync(request, HttpResponse.BodyHandlers.ofString());
HttpResponse<String> asyncResponse = future.join();
System.out.println("Async Response: " + asyncResponse.body());
}
}
在这个示例中,我们首先创建了一个HttpClient
实例,然后构建了一个GET请求,并使用HttpClient.send
方法发送同步请求。同时,我们还展示了如何使用异步请求发送器sendAsync
,通过CompletableFuture
来处理异步请求的响应。
解析响应内容
除了直接发送请求并获取响应外,还可以对响应内容进行进一步的处理。例如,解析JSON响应并提取特定的数据字段。
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
public class ParseResponseExample {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建HttpClient实例
HttpClient client = HttpClient.newHttpClient();
// 构建HTTP请求
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/data"))
.build();
// 发送同步请求
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
// 解析响应内容
ObjectMapper mapper = new ObjectMapper();
JsonNode jsonNode = mapper.readTree(response.body());
String name = jsonNode.get("name").asText();
int age = jsonNode.get("age").asInt();
System.out.println("Name: " + name);
System.out.println("Age: " + age);
}
}
在这个示例中,我们使用Jackson库解析了HTTP响应的JSON内容,提取了name
和age
字段,并输出了这些字段的值。这种方式使得处理复杂的HTTP响应变得更加简单和高效。
案例:发送POST请求
在某些场景下,需要发送POST请求来传递数据。新的HTTP客户端API同样支持POST请求,可以携带JSON数据或表单数据。
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpClient;
import java.net.http.HttpRequest.BodyPublishers;
import java.util.concurrent.CompletableFuture;
public class PostRequestExample {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建HttpClient实例
HttpClient client = HttpClient.newHttpClient();
// 构建POST请求
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/submit"))
.POST(BodyPublishers.ofString("{\"name\":\"John\", \"email\":\"john@example.com\"}"))
.build();
// 发送同步请求
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println("Response: " + response.body());
}
}
在这一示例中,我们构建了一个POST请求,并通过BodyPublishers.ofString
方法将JSON数据作为请求体发送。这种方式适合发送JSON格式的数据,也可以通过BodyPublishers.ofFormData
方法发送表单数据。
案例:使用连接池
在处理大量并发请求的情况下,使用连接池可以显著提高性能。新的HTTP客户端API支持连接池,可以更高效地管理连接资源。
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ConnectionPoolExample {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建HttpClient实例,并指定连接池
HttpClient client = HttpClient.newHttpClient().newBuilder()
.executor(Executors.newFixedThreadPool(10)) // 使用固定线程池
.build();
// 发送多个请求
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
final int index = i;
executor.submit(() -> {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/data"))
.build();
try {
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(index + ": " + response.body());
} catch (Exception e) {
e.printStackTrace();
}
});
}
executor.shutdown();
}
}
在这个示例中,我们使用HttpClient.newBuilder().executor(Executors.newFixedThreadPool(10))
来创建一个带有连接池的HttpClient
实例。然后,我们使用一个固定线程池来并发处理多个请求,每个请求都使用同一个连接池来发送HTTP请求。
案例:处理流式响应
在处理大数据量或流式数据时,新的HTTP客户端API支持按块读取响应内容,从而避免一次性加载大量数据。这种方式适用于大文件传输或实时数据流处理。
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
public class StreamingResponseExample {
public static void main(String[] args) throws Exception {
// 创建HttpClient实例
HttpClient client = HttpClient.newHttpClient();
// 构建HTTP请求
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/largefile"))
.build();
// 发送请求并处理流式响应
client.sendAsync(request, HttpResponse.BodyHandlers.ofInputStream())
.thenApply(HttpResponse::body)
.thenAccept(inputStream -> {
Path file = Path.of("output.txt");
try {
Files.copy(inputStream, file, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
System.out.println("File saved to " + file);
} catch (Exception e) {
e.printStackTrace();
}
})
.join();
}
}
在这个示例中,我们使用HttpResponse.BodyHandlers.ofInputStream
来处理流式响应。我们将响应内容保存到一个文件中,避免一次性加载大量数据。
通过上述示例,可以看出新的HTTP客户端API在各种场景下的强大功能和灵活性。无论是同步请求、异步请求、连接池、流式处理还是POST请求,新的API都提供了简洁而强大的解决方案。
移除Java EE和CORBA模块在JDK 11中,Oracle决定移除Java EE和CORBA模块,以简化JDK的结构并引导开发者使用更现代、更轻量的替代方案。Java EE模块的移除意味着核心Java平台不再包含Java EE相关的API,如Java Servlet、Java Persistence API(JPA)、Java API for RESTful Services (JAX-RS)等。而CORBA模块的移除则意味着不再支持基于CORBA的分布式对象技术。这些变化对开发者的日常工作影响较大,需要在项目中使用替代方案。
移除的模块介绍
Java EE模块是一套针对企业级应用的API和规范集合,包括Servlet、JSP、EJB、JPA等。在JDK 11中,这些模块被移除,取而代之的是Jakarta EE,这是一个由Eclipse基金会管理的新规范集合,继承了Java EE的大部分功能,并进行了改进和优化。Jakarta EE在项目中可以独立使用,也可以与核心Java平台结合使用。
CORBA模块则是一套用于实现分布式对象技术的API,它允许不同语言和不同操作系统上的对象进行互操作。随着现代技术的发展,CORBA技术逐渐被淘汰,被更现代的轻量级解决方案所取代。
对开发的影响
移除Java EE和CORBA模块对开发者的日常编程工作产生了显著影响。首先,开发者需要寻找替代方案来满足原有的企业级应用开发需求。这通常意味着转向Jakarta EE或者利用Spring Boot、Micronaut等现代框架来构建企业级应用。这些替代方案提供了与Java EE类似的强大功能,同时更加轻量和灵活。
对于已经使用Java EE的企业级应用,需要重新评估和迁移现有代码库,确保兼容性或进行必要的重构。此外,对于依赖CORBA技术的应用,可能需要寻找新的分布式对象技术解决方案,如gRPC、Apache Thrift或者基于REST的API等。
移除模块的替代方案
Java EE -> Jakarta EE 或 Spring Boot / Micronaut
Java EE模块的移除意味着开发者需要寻找新的企业级开发框架来替代原有的功能。Jakarta EE提供了与Java EE相似的API和规范,而Spring Boot和Micronaut则是更为现代和灵活的框架。
- Jakarta EE:Jakarta EE是Java EE的继承者,提供了丰富的企业级API,如Servlet、JPA、JAX-RS等。开发者可以继续使用这些API来构建企业级应用。
- Spring Boot:Spring Boot是一个基于Spring框架的轻量级企业级应用开发框架。它提供了自动配置和依赖管理,使得构建企业级应用变得更加简单。
- Micronaut:Micronaut是一个现代的、响应式的、功能性的企业级应用开发框架。它支持Java 8,具有编译时依赖注入和响应式编程支持。
CORBA -> gRPC 或 Apache Thrift 或 REST API
对于依赖CORBA的应用,开发者可以选择现代的分布式对象技术来替代。gRPC和Apache Thrift是两个流行的RPC框架,可以用于构建高性能的分布式系统。
- gRPC:gRPC是一个高性能、开源的RPC框架,支持多种语言和平台。它使用Protocol Buffers作为数据交换格式,可以确保跨语言和平台的互操作性。
- Apache Thrift:Apache Thrift是一个跨语言的服务开发框架,支持多种编程语言,并提供了一套丰富的数据交换协议。它非常适合构建大型分布式系统。
- REST API:对于简单的分布式对象交互,可以考虑使用基于HTTP/REST的API。这种方式更加简单和灵活,适用于大多数应用场景。
示例代码演示
下面通过一个简单的示例来展示如何使用Spring Boot替代原有的Java EE功能。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
public class SpringBootExampleApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootExampleApplication.class, args);
}
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "Hello, World!";
}
}
}
在这一示例中,我们使用Spring Boot构建了一个简单的REST服务。通过定义一个带有@RestController
注解的类,以及一个带有@GetMapping
注解的方法,实现了对外部请求的响应。这种方式替代了原有的Java EE风格的Servlet结构,提供了更简洁和灵活的解决方案。
通过上述示例,可以看出如何迁移原有的企业级应用到新的框架中,以替代被移除的Java EE和CORBA模块。开发者可以结合现代的框架和工具,继续构建强大且灵活的企业级应用。
新的飞行记录器API新的飞行记录器API是JDK 11中引入的一项重要特性,旨在为开发者提供一种工具来记录JVM的运行时状态。这些记录可以用于诊断问题、性能分析或在生产环境中进行故障排除。飞行记录器API允许开发者通过配置来收集和过滤特定的事件和指标,从而提供灵活的监控和调试能力。
飞行记录器API的使用
飞行记录器API的核心是jdk.jfr
包,它提供了一系列类和接口来配置和管理飞行记录器功能。开发者可以通过自定义的事件类来定义要记录的数据,然后通过配置文件来启用或禁用这些事件的记录。飞行记录器API还支持实时查看记录的数据,以及将记录的数据导出为文件,以便后续分析。
示例代码展示
下面通过一个简单的示例来展示如何使用新的飞行记录器API来记录一些基本的事件。
import jdk.jfr.Event;
import jdk.jfr.Label;
import jdk.jfr.Name;
@Name("com.example.SimpleEvent")
@Label("Simple Event")
public class SimpleEvent extends Event {
public String name;
public int count;
public SimpleEvent(String name, int count) {
this.name = name;
this.count = count;
this.commit();
}
}
public class FlightRecorderExample {
public static void main(String[] args) {
// 记录一个简单的事件
SimpleEvent event = new SimpleEvent("example", 100);
// 输出事件信息
System.out.println("Event: " + event.name + ", Count: " + event.count);
}
}
在这个示例中,我们定义了一个自定义的事件类SimpleEvent
。该事件类继承了Event
类,并定义了name
和count
两个字段。通过构造函数来创建事件对象,并调用commit
方法来记录这个事件。在main
方法中,我们创建了一个SimpleEvent
对象,并输出了事件的名称和计数。
解析记录的数据
除了简单的事件记录外,还可以使用飞行记录器API来记录更复杂的数据,并通过解析这些数据来进行故障诊断或性能分析。
import jdk.jfr.consumer.EventProvider;
import jdk.jfr.consumer.RecordedEvent;
import jdk.jfr.consumer.Recorder;
import java.util.List;
public class RecordAnalysisExample {
public static void main(String[] args) {
// 启用飞行记录器
Recorder recorder = Recorder.getRecordedEvents().get(0);
recorder.start();
// 记录一些事件
SimpleEvent event1 = new SimpleEvent("example1", 100);
SimpleEvent event2 = new SimpleEvent("example2", 200);
// 停止记录
recorder.stop();
// 解析记录的数据
EventProvider provider = recorder.getProvider();
List<RecordedEvent> recordedEvents = provider.getEvents(SimpleEvent.class);
// 输出解析后的事件
for (RecordedEvent event : recordedEvents) {
System.out.println("Event: " + event.get("name") + ", Count: " + event.get("count"));
}
}
}
在这一示例中,我们首先启用了飞行记录器,并开始记录事件。创建了两个SimpleEvent
对象来记录一些数据。然后,我们停止了记录,使用EventProvider
从飞行记录器中获取记录的数据,并解析这些数据。最后,我们输出了解析后的每个事件的名称和计数。
配置飞行记录器
除了简单的事件记录外,还可以通过配置文件来启用或禁用特定的事件,以及设置其他配置选项。飞行记录器支持多种配置文件格式,包括XML和JSON。
import jdk.jfr.Configuration;
import jdk.jfr.Recording;
public class FlightRecorderConfigExample {
public static void main(String[] args) {
// 创建一个新的配置
Configuration config = new Configuration("my-config");
// 启用特定的事件
config.set("com.example.SimpleEvent", true);
// 创建一个新的记录并应用配置
Recording recording = new Recording();
recording.setConfiguration(config);
recording.start();
// 记录一些事件
SimpleEvent event = new SimpleEvent("example", 100);
// 停止记录
recording.stop();
}
}
在这个示例中,我们创建了一个新的配置对象config
,并启用了名为com.example.SimpleEvent
的事件。然后,我们创建了一个新的记录对象recording
,并应用了这个配置。通过这种方式,可以灵活地控制飞行记录器的行为和记录的数据。
通过上述示例,可以看出如何使用新的飞行记录器API来记录和解析JVM的运行时状态。这为开发者提供了强大的工具来诊断问题和优化性能。
并发改进与ZGC和ShenandoahGCJDK 11中的一个重要改进是引入了新的垃圾回收器(GC)实现,即ZGC和Shenandoah GC。这些新的GC实现旨在解决大型应用中的性能瓶颈和内存泄漏问题,从而提高应用的响应性和稳定性。ZGC和Shenandoah GC都是基于连续内存模型设计的,可以有效减少垃圾回收暂停时间,提高系统性能。
并发改进介绍
在传统的垃圾回收器中,如G1和CMS,垃圾回收过程可能会导致较长时间的暂停,这在高并发和实时应用中是不可接受的。ZGC和Shenandoah GC通过采用不同的算法和数据结构,减少了垃圾回收暂停时间,允许应用程序在垃圾回收过程中继续运行,从而提高了系统的整体性能和响应性。
ZGC (Z Garbage Collector)
ZGC是一种采用读屏障技术的垃圾回收器,它可以在几乎不停止应用程序的情况下进行垃圾回收。ZGC通过使用染色指针和染色区域来跟踪对象的引用关系,从而避免了传统GC中的大量暂停时间。ZGC可以在多核系统中高效运行,适用于需要低延迟的应用场景。
Shenandoah GC
Shenandoah GC同样是一种针对低延迟设计的垃圾回收器,它采用标记-清理算法,并通过并行和并发的方式进行垃圾回收。Shenandoah GC将堆内存划分为多个区域,并通过并行处理这些区域来减少垃圾回收暂停时间。Shenandoah GC利用了多核处理器的优势,通过并行处理多个内存区域,避免了长时间的暂停。Shenandoah GC还支持增量标记,可以在应用运行期间逐步标记堆内存中的对象,从而减少垃圾回收的暂停时间。
ZGC和ShenandoahGC的简要讲解
ZGC的工作原理
ZGC的核心原理是使用染色指针和染色区域来跟踪对象的引用关系。具体来说,ZGC将堆内存划分为多个区域,每个区域包含一个固定大小的染色指针。这些指针用于记录对象的引用关系,使得垃圾回收器可以追踪到所有活跃的对象。在垃圾回收过程中,ZGC通过读屏障技术,确保引用关系的正确性,从而避免了传统GC中的暂停时间。
Shenandoah GC的工作原理
Shenandoah GC同样采用了标记-清理算法,但通过并行和并发的方式进行垃圾回收。Shenandoah GC将堆内存划分为多个区域,并通过并行处理这些区域来减少垃圾回收暂停时间。Shenandoah GC利用了多核处理器的优势,通过并行处理多个内存区域,避免了长时间的暂停。Shenandoah GC还支持增量标记,可以在应用运行期间逐步标记堆内存中的对象,从而减少垃圾回收的暂停时间。
示例代码演示
下面通过一个简单的示例来展示如何启用ZGC和Shenandoah GC。
启用ZGC
public class ZGCExample {
public static void main(String[] args) {
// 启用ZGC
System.setProperty("jdk.garbage.collector.name", "ZGC");
// 创建并使用一个大对象
byte[] largeObject = new byte[1024 * 1024 * 100]; // 100MB
System.out.println("Large object created.");
// 运行一段长时间的操作
for (int i = 0; i < 1000000; i++) {
// 模拟长时间的操作
}
}
}
在这个示例中,我们通过设置系统属性jdk.garbage.collector.name
来启用ZGC。然后,创建了一个100MB的大对象,并运行一段长时间的操作。ZGC会在后台进行垃圾回收,而不会导致应用程序暂停。
启用Shenandoah GC
public class ShenandoahGCExample {
public static void main(String[] args) {
// 启用Shenandoah GC
System.setProperty("jdk.garbage.collector.name", "ShenandoahGC");
// 创建并使用一个大对象
byte[] largeObject = new byte[1024 * 1024 * 100]; // 100MB
System.out.println("Large object created.");
// 运行一段长时间的操作
for (int i = 0; i < 1000000; i++) {
// 模拟长时间的操作
}
}
}
在这个示例中,我们通过设置系统属性jdk.garbage.collector.name
来启用Shenandoah GC。同样,创建了一个100MB的大对象,并运行一段长时间的操作。Shenandoah GC会在后台进行垃圾回收,而不会导致应用程序暂停。
通过上述示例,可以看出如何启用ZGC和Shenandoah GC,并在实际应用中使用这些新的垃圾回收器来提高性能和响应性。这些新的GC实现使得处理大规模内存的应用变得更加高效和稳定。
共同学习,写下你的评论
评论加载中...
作者其他优质文章