Java 11 相比 Java 8 引入了许多新的语言特性和 API,下面是一些主要的特性:
-
HTTP Client API:Java 11 中引入了一个全新的原生 HTTP 客户端 API,用于替代老旧的 HttpURLConnection API。
-
动态类文件常量:Java 11 引入了动态类文件常量,可以在不加载类的情况下,将常量加入到已有的类定义中。
-
单元测试优化:Java 11 改善了单元测试的执行体验,引入了 @BeforeEach 和 @AfterEach 注解,简化了测试用例中的重复代码。
-
Stream API增强:Java 11 增加了一些新的 Stream API 操作,例如 takeWhile() 和 dropWhile() 等方法。
-
本地变量类型推断增强:在 Java 10 中,引入了 var 关键字,可以让编译器推断出变量的类型。Java 11 进一步扩展了这个特性,在 Lambda 表达式、匿名内部类、方法引用等场景下也可使用 var 定义局部变量。
-
ZGC:Java 11 引入了一个新的垃圾收集器 ZGC,它是一个高吞吐、低延迟的垃圾收集器,适用于大型内存应用程序。
除此之外,Java 11 还引入了其他一些小的改进,例如更好的 Unicode 支持、标准化 HTTP/2 客户端、废弃 Nashorn JavaScript 引擎等等。
垃圾收集器 ZGC
垃圾收集器 ZGC(Z Garbage Collector),它是一款可伸缩的、低停顿的垃圾收集器,专为大内存应用程序设计。下面是对 ZGC 的详细介绍:
-
低停顿:ZGC 的最大特点就是低停顿,即无论堆大小如何,暂停时间都不会超过 10ms。这是通过将垃圾收集任务分成小块,并与应用程序线程一起执行来实现的。
-
可伸缩:ZGC 是一款可伸缩的垃圾收集器,它能够处理从几百兆字节到几个太字节的堆。它可以自动调整线程数和内存使用量,以最大化吞吐量并尽可能减小延迟。
-
分代:ZGC 具有分代特性,它将 Java 对象分为几个年龄段,根据不同的年龄段进行垃圾收集。这种分代方式可以减少垃圾回收的工作量,提高垃圾回收效率。
-
关注全局吞吐量:与其他低延迟垃圾收集器不同,ZGC 关注的是全局吞吐量,而不是单个垃圾收集操作的延迟。这意味着,在任何给定时间内,ZGC 都会尽可能多地收集垃圾。
-
空间回收:ZGC 采用分布式的空间回收方式,它将堆空间分成多个区域,并同时进行垃圾回收和内存压缩操作,以最小化空间浪费和碎片。
ZGC运作过程
ZGC的运作过程大致可划分为以下四个大的阶段:
- 并发标记(Concurrent Mark):与G1一样,并发标记是遍历对象图做可达性分析的阶段,它的初始标记 (Mark Start)和最终标记(Mark End)也会出现短暂的停顿,与G1不同的是, ZGC的标记是在指针上而不是在对象 上进行的, 标记阶段会更新染色指针中的Marked 0、 Marked 1标志位。
- 并发预备重分配(Concurrent Prepare for Relocate):这个阶段需要根据特定的查询条件统计得出本次收 集过程要清理哪些Region,将这些Region组成重分配集(Relocation Set)。ZGC每次回收都会扫描所有的 Region,用范围更大的扫描成本换取省去G1中记忆集的维护成本。
- 并发重分配(Concurrent Relocate):重分配是ZGC执行过程中的核心阶段,这个过程要把重分配集中的存 活对象复制到新的Region上,并为重分配集中的每个Region维护一个转发表(Forward Table),记录从旧对象 到新对象的转向关系。ZGC收集器能仅从引用上就明确得知一个对象是否处于重分配集之中,如果用户线程此时并 发访问了位于重分配集中的对象,这次访问将会被预置的内存屏障(读屏障)所截获,然后立即根据Region上的转发 表记录将访问转发到新复制的对象上,并同时修正更新该引用的值,使其直接指向新对象,ZGC将这种行为称为指 针的“自愈”(Self-Healing)能力。 ZGC的颜色指针因为“自愈”(Self‐Healing)能力,所以只有第一次访问旧对象会变慢, 一旦重分配集中某个Region的存活对象都复制完毕 后,这个Region就可以立即释放用于新对象的分配,但是转发表还得留着不能释放掉, 因为可能还有访问在使用这个转发表。
- 并发重映射(Concurrent Remap):重映射所做的就是修正整个堆中指向重分配集中旧对象的所有引用,但 是ZGC中对象引用存在“自愈”功能,所以这个重映射操作并不是很迫切。ZGC很巧妙地把并发重映射阶段要做的 工作,合并到了下一次垃圾收集循环中的并发标记阶段里去完成,反正它们都是要遍历所有对象的,这样合并就节 省了一次遍历对象图的开销。一旦所有指针都被修正之后, 原来记录新旧对象关系的转发表就可以释放掉了。
总之,ZGC 是一款非常强大的垃圾收集器,它通过低停顿、可伸缩、分代、关注全局吞吐量和分布式空间回收等特性,实现了高效、健壮的垃圾收集。如果你有大内存应用程序的需求,那么 ZGC 就是一个值得尝试的选择。
HTTP Client API
全新的 HTTP Client API,用于替代老旧的 HttpURLConnection API。相对于 HttpURLConnection,Java 11 中的 HTTP Client API 具有更好的设计、更好的性能、更好的可读性。
下面是一些 Java 11 HTTP Client API 的主要特点:
-
异步非阻塞:Java 11 中的 HTTP Client API 默认是异步非阻塞的,可以让应用程序在发送请求时不被阻塞。
-
响应流:HTTP Client API 支持从响应中获取流,这意味着在处理大文件或流式数据时,不必等待整个响应读取完毕。
-
WebSocket 支持:Java 11 中的 HTTP Client API 也支持 WebSocket,可以与服务器交互并传递消息。
-
HTTP/2 支持:HTTP Client API 支持 HTTP/2 协议,同时也支持老旧的 HTTP/1.1 协议。
-
Cookie 管理:HTTP Client API 提供了一个简单的 cookie 管理器,用于处理与请求和响应相关的 cookie。
下面是一个使用 Java 11 HTTP Client API 发送 GET 请求的示例代码:
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Example {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://example.com"))
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
下面是一个使用 Java 11 HTTP Client API 发送 POST 请求的示例代码:
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
public class Example {
public static void main(String[] args) throws IOException, InterruptedException {
// 创建 HttpClient 实例
HttpClient client = HttpClient.newHttpClient();
// 构造请求体内容
String requestBody = "username=" + URLEncoder.encode("testuser", StandardCharsets.UTF_8) +
"&password=" + URLEncoder.encode("testpassword", StandardCharsets.UTF_8);
// 创建 POST 请求
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://example.com/login"))
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
.build();
// 发送请求并获取响应
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
// 输出响应内容
System.out.println(response.body());
}
}
这个示例代码通过构建带有请求体的 POST 请求,向远程服务发送数据,并获取响应结果。在本例中,我们使用 application/x-www-form-urlencoded
格式发送请求,将用户名和密码作为请求体的一部分发送给服务器。根据实际情况,可以更改请求头和请求体的格式来适应不同的场景。
动态类文件常量
动态类文件常量(Dynamic Constants),它允许在不修改类定义的情况下,将常量添加到已有的类定义中。实现方式是通过 javac 编译器在编译时创建一个常量属性,并在运行时读取该属性。
动态类文件常量的主要特点:
- 可以通过引入常量值的方式来扩展类定义,无需修改现有代码;
- 常量的值可以在运行时计算,这意味着它可以是任何合法的 Java 表达式;
- 常量是只读的,无法在运行时修改。
使用示例:
public class Example {
// 使用 dynamic 声明常量,在编译时将其添加到 class 文件的常量池中
private static final String myConst = dynamic("Hello, world!");
public static void main(String[] args) {
System.out.println(myConst);
}
// 定义 dynamic 方法,用于返回常量
private static String dynamic(String s) {
return s.toUpperCase();
}
}
在这个示例中,我们使用 dynamic
方法声明了一个字符串常量 myConst
,并将其值设置为 'Hello, world!'
的大写形式。由于这是一个动态常量,因此它的值只能在运行时计算,而不能在编译时确定。运行程序后,输出将是 "HELLO, WORLD!"
。
需要注意的是,动态类文件常量应该被谨慎使用,因为它会增加类文件的大小,可能会导致更大的启动时间和内存占用。
单元测试优化
Java 11 改善了单元测试的执行体验,引入了 @BeforeEach 和 @AfterEach 注解,简化了测试用例中的重复代码。
单元测试的改进,其中最突出的改进是对 assert
方法的增强。具体来说,Java 11 引入了一个新的关键字 var
,可以在 assert
语句中使用。
下面是一个简单的示例:
int num = 5;
assert var result = num > 0;
在这个示例中,我们使用 var
关键字声明了一个变量 result
,并将它和断言语句结合起来。如果 num
大于 0
,则断言成功,程序继续执行;否则,程序会抛出一个 AssertionError
。
这种改进的主要优点是它可以使我们在 assert
语句中更灵活地使用本地变量。在以前的版本中,我们必须将变量声明和初始化都放在断言语句之外,这会导致代码冗长和混乱。
除此之外,Java 11 还增强了 JUnit5 中的测试框架,具体来说,JUnit5 引入了以下一些新功能:
-
@DisplayName
注解:可以为测试用例添加一个自定义的名称; -
@Nested
注解:可以嵌套其他测试类; -
@Tag
注解:可以为测试用例添加一个自定义的标签,方便分类和过滤测试用例; - 改进了测试生命周期方法(BeforeAll、AfterAll、BeforeEach、AfterEach)的可见性。
这些新功能进一步提高了单元测试的可读性、可维护性和可扩展性,使得单元测试在软件开发中的重要性更加突出。
@BeforeEach
和 @AfterEach
是 JUnit5 测试框架中的两个测试生命周期注解,它们分别用于在每个测试方法执行之前和之后运行一些代码。
具体来说,@BeforeEach
注解可以用于标记一个方法,在每个测试方法执行之前都会运行这个方法。通常情况下,我们可以在这个方法中做一些准备工作,比如初始化测试数据、建立测试环境等。这样可以保证每个测试方法在执行之前都拥有一个相同的状态,避免了测试互相干扰的情况发生。
下面是一个简单的示例:
public class MyTest {
@BeforeEach
void init() {
// 这里可以进行一些测试准备工作
}
@Test
void testMethod1() {
// 测试方法1
}
@Test
void testMethod2() {
// 测试方法2
}
@AfterEach
void cleanup() {
// 这里可以进行一些测试清理工作
}
}
在这个示例中,我们定义了一个测试类 MyTest
,并使用 @BeforeEach
和 @AfterEach
注解定义了两个方法 init()
和 cleanup()
。在每个测试方法执行之前和之后,JUnit5 都会自动调用这两个方法,以便进行一些准备工作和清理工作。
需要注意的是,@BeforeEach
和 @AfterEach
注解只能用于非静态方法且不能被继承,也就是说,它们只会影响当前测试类中的测试方法,而不会影响父类或子类中的测试方法。
总之,@BeforeEach
和 @AfterEach
注解为我们提供了一个方便的方式,在每个测试方法执行之前和之后运行一些代码,并保证测试方法之间的独立性。
Stream API增强
Java 11 在 Stream API 中新增了一些方法,其中一些方法是对现有方法的扩展,而另一些方法则是全新的。下面简要介绍几个 Java 11 中新增的 Stream API 方法:
-
takeWhile()
和dropWhile()
方法:这两个方法允许从流中获取满足(或不满足)特定条件的元素,并返回一个新的流。takeWhile()
方法获取满足条件的元素,并在遇到第一个不满足条件的元素时停止。dropWhile()
方法则剔除满足条件的元素,并在遇到第一个不满足条件的元素时开始保留元素。
使用示例:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
// 使用 takeWhile() 方法获取满足条件的元素
List<Integer> evenNumbers = numbers.stream()
.takeWhile(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println(evenNumbers); // 输出 [2]
// 使用 dropWhile() 方法过滤满足条件的元素
List<Integer> oddNumbers = numbers.stream()
.dropWhile(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println(oddNumbers); // 输出 [1, 3, 4, 5, 6]
2.ofNullable()
方法:这个方法允许我们在创建流时处理可能为空的数据对象,它会将非空元素包装在一个单元素流中,否则返回一个空流。
使用示例:
String name = null;
// 使用 ofNullable() 方法创建一个包含 name 的流
Stream<String> stream = Stream.ofNullable(name);
System.out.println(stream.count()); // 输出 0
3.iterate() 方法增加了一个新的重载方法,它接收谓词函数(Predicate)作为第二个参数,用于定义何时停止迭代。这个方法允许我们按需生成无限流示例。
使用示例:
Stream.iterate(2, n -> n < 100, n -> n * n)
.forEach(System.out::println);
这段代码将使用 iterate() 方法生成一个无限流,每次平方之后增加两倍,直到达到 100。输出结果将是 2 4 16 256
。
这些是 Java 11 中新增的 Stream API 方法之一,它们可以大大提高 Stream API 的灵活性和可用性。
本地变量类型推断增强
Java 11 对 var
关键字进行了改进,主要是使其可以在一些特殊场景下使用,具体如下:
-
var
可以在lambda
表达式中使用。在以前的 Java 版本中,如果要声明一个无类型的变量,需要使用匿名内部类或明确指定类型。而在 Java 11 中,我们可以使用var
关键字来简化这个过程。例如,下面的代码展示了如何在 lambda 表达式中使用
var
关键字:List<String> list = new ArrayList<>(); list.add("hello"); list.add("world"); list.forEach((var s) -> System.out.println(s));
在这个示例中,我们使用了
var
关键字来声明一个无类型的变量s
,并将它用于forEach()
方法中的 lambda 表达式中。 -
var
可以在try-with-resources
语句中使用。在以前的 Java 版本中,如果要在try-with-resources
语句中使用多个资源,需要显式地为每个资源指定类型。而在 Java 11 中,我们可以使用var
关键字来简化这个过程。
例如,下面的代码展示了如何在 try-with-resources
语句中使用 var
关键字:
try (var reader = new BufferedReader(new FileReader("file.txt"));
var writer = new BufferedWriter(new FileWriter("out.txt"))) {
// 处理文件读写操作
}
在这个示例中,我们使用了 var
关键字来声明两个无类型的变量 reader
和 writer
,并将它们用于 try-with-resources
语句中。文章来源:https://www.toymoban.com/news/detail-588921.html
需要注意的是,虽然 var
关键字可以在一些特殊场景下使用,但是它不应该被滥用。在大多数情况下,我们仍然应该为变量指定明确的类型,以提高代码的可读性和可维护性。文章来源地址https://www.toymoban.com/news/detail-588921.html
到了这里,关于JDK11相比JDK1.8有哪些新特性的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!