JDK11相比JDK1.8有哪些新特性

Java 11 相比 Java 8 引入了许多新的语言特性和 API,下面是一些主要的特性:

  1. HTTP Client API:Java 11 中引入了一个全新的原生 HTTP 客户端 API,用于替代老旧的 HttpURLConnection API。

  2. 动态类文件常量:Java 11 引入了动态类文件常量,可以在不加载类的情况下,将常量加入到已有的类定义中。

  3. 单元测试优化:Java 11 改善了单元测试的执行体验,引入了 @BeforeEach 和 @AfterEach 注解,简化了测试用例中的重复代码。

  4. Stream API增强:Java 11 增加了一些新的 Stream API 操作,例如 takeWhile() 和 dropWhile() 等方法。

  5. 本地变量类型推断增强:在 Java 10 中,引入了 var 关键字,可以让编译器推断出变量的类型。Java 11 进一步扩展了这个特性,在 Lambda 表达式、匿名内部类、方法引用等场景下也可使用 var 定义局部变量。

  6. ZGC:Java 11 引入了一个新的垃圾收集器 ZGC,它是一个高吞吐、低延迟的垃圾收集器,适用于大型内存应用程序。

除此之外,Java 11 还引入了其他一些小的改进,例如更好的 Unicode 支持、标准化 HTTP/2 客户端、废弃 Nashorn JavaScript 引擎等等。

垃圾收集器 ZGC

垃圾收集器 ZGC(Z Garbage Collector),它是一款可伸缩的、低停顿的垃圾收集器,专为大内存应用程序设计。下面是对 ZGC 的详细介绍:

  1. 低停顿:ZGC 的最大特点就是低停顿,即无论堆大小如何,暂停时间都不会超过 10ms。这是通过将垃圾收集任务分成小块,并与应用程序线程一起执行来实现的。

  2. 可伸缩:ZGC 是一款可伸缩的垃圾收集器,它能够处理从几百兆字节到几个太字节的堆。它可以自动调整线程数和内存使用量,以最大化吞吐量并尽可能减小延迟。

  3. 分代:ZGC 具有分代特性,它将 Java 对象分为几个年龄段,根据不同的年龄段进行垃圾收集。这种分代方式可以减少垃圾回收的工作量,提高垃圾回收效率。

  4. 关注全局吞吐量:与其他低延迟垃圾收集器不同,ZGC 关注的是全局吞吐量,而不是单个垃圾收集操作的延迟。这意味着,在任何给定时间内,ZGC 都会尽可能多地收集垃圾。

  5. 空间回收:ZGC 采用分布式的空间回收方式,它将堆空间分成多个区域,并同时进行垃圾回收和内存压缩操作,以最小化空间浪费和碎片。

ZGC运作过程

ZGC的运作过程大致可划分为以下四个大的阶段:

 

  1. 并发标记(Concurrent Mark):与G1一样,并发标记是遍历对象图做可达性分析的阶段,它的初始标记 (Mark Start)和最终标记(Mark End)也会出现短暂的停顿,与G1不同的是, ZGC的标记是在指针上而不是在对象 上进行的, 标记阶段会更新染色指针中的Marked 0、 Marked 1标志位。
  2. 并发预备重分配(Concurrent Prepare for Relocate):这个阶段需要根据特定的查询条件统计得出本次收 集过程要清理哪些Region,将这些Region组成重分配集(Relocation Set)。ZGC每次回收都会扫描所有的 Region,用范围更大的扫描成本换取省去G1中记忆集的维护成本。
  3. 并发重分配(Concurrent Relocate):重分配是ZGC执行过程中的核心阶段,这个过程要把重分配集中的存 活对象复制到新的Region上,并为重分配集中的每个Region维护一个转发表(Forward Table),记录从旧对象 到新对象的转向关系。ZGC收集器能仅从引用上就明确得知一个对象是否处于重分配集之中,如果用户线程此时并 发访问了位于重分配集中的对象,这次访问将会被预置的内存屏障(读屏障)所截获,然后立即根据Region上的转发 表记录将访问转发到新复制的对象上,并同时修正更新该引用的值,使其直接指向新对象,ZGC将这种行为称为指 针的“自愈”(Self-Healing)能力。 ZGC的颜色指针因为“自愈”(Self‐Healing)能力,所以只有第一次访问旧对象会变慢, 一旦重分配集中某个Region的存活对象都复制完毕 后,这个Region就可以立即释放用于新对象的分配,但是转发表还得留着不能释放掉, 因为可能还有访问在使用这个转发表。
  4. 并发重映射(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 的主要特点:

  1. 异步非阻塞:Java 11 中的 HTTP Client API 默认是异步非阻塞的,可以让应用程序在发送请求时不被阻塞。

  2. 响应流:HTTP Client API 支持从响应中获取流,这意味着在处理大文件或流式数据时,不必等待整个响应读取完毕。

  3. WebSocket 支持:Java 11 中的 HTTP Client API 也支持 WebSocket,可以与服务器交互并传递消息。

  4. HTTP/2 支持:HTTP Client API 支持 HTTP/2 协议,同时也支持老旧的 HTTP/1.1 协议。

  5. 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 编译器在编译时创建一个常量属性,并在运行时读取该属性。

动态类文件常量的主要特点:

  1. 可以通过引入常量值的方式来扩展类定义,无需修改现有代码;
  2. 常量的值可以在运行时计算,这意味着它可以是任何合法的 Java 表达式;
  3. 常量是只读的,无法在运行时修改。

使用示例:

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 引入了以下一些新功能:

  1. @DisplayName 注解:可以为测试用例添加一个自定义的名称;
  2. @Nested 注解:可以嵌套其他测试类;
  3. @Tag 注解:可以为测试用例添加一个自定义的标签,方便分类和过滤测试用例;
  4. 改进了测试生命周期方法(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 方法:

  1. 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 关键字进行了改进,主要是使其可以在一些特殊场景下使用,具体如下:

  1. 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 表达式中。

  2. 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 关键字来声明两个无类型的变量 readerwriter,并将它们用于 try-with-resources 语句中。

需要注意的是,虽然 var 关键字可以在一些特殊场景下使用,但是它不应该被滥用。在大多数情况下,我们仍然应该为变量指定明确的类型,以提高代码的可读性和可维护性。

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值