Java 8新特性

Lambda表达式、接口的方法类型、Stream流、optional、方法引用、日期类、HashMap、元空间和永久代

image.png

1.Interface

interface 的设计初衷是面向抽象,提高扩展性。这也留有一点遗憾,Interface 修改的时候,实现它的类也必须跟着改。

为了解决接口的修改与现有的实现不兼容的问题。新 interface 的方法可以用defaultstatic修饰,这样就可以有方法体,实现类也不必重写此方法。

一个 interface 中可以有多个方法被它们修饰,这 2 个修饰符的区别主要也是普通方法和静态方法的区别。

  1. default修饰的方法,是普通实例方法,可以用this调用,可以被子类继承、重写。
  2. static修饰的方法,使用上和一般类静态方法一样。但它不能被子类继承,只能用Interface调用。

2.Lambda 表达式

接下来谈众所周知的 Lambda 表达式。它是推动 Java 8 发布的最重要新特性。是继泛型(Generics)和注解(Annotation)以来最大的变化。

使用 Lambda 表达式可以使代码变的更加简洁紧凑。让 java 也能支持简单的_函数式编程_。

Lambda 表达式是一个匿名函数,java 8 允许把函数作为参数传递进方法中。

1
2
3
(parameters) -> expression 或
(parameters) ->{ statements; }

3.方法引用

Java 8 允许使用 :: 关键字来传递方法或者构造函数引用,无论如何,表达式返回的类型必须是 functional-interface。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class LambdaClassSuper {
LambdaInterface sf(){
return null;
}
}

public class LambdaClass extends LambdaClassSuper {
public static LambdaInterface staticF() {
return null;
}

public LambdaInterface f() {
return null;
}

void show() {
//1.调用静态函数,返回类型必须是functional-interface
LambdaInterface t = LambdaClass::staticF;

//2.实例方法调用
LambdaClass lambdaClass = new LambdaClass();
LambdaInterface lambdaInterface = lambdaClass::f;

//3.超类上的方法调用
LambdaInterface superf = super::sf;

//4. 构造方法调用
LambdaInterface tt = LambdaClassSuper::new;
}
}

4.Stream

java 新增了 java.util.stream 包,它和之前的流大同小异。之前接触最多的是资源流,比如java.io.FileInputStream,通过流把文件从一个地方输入到另一个地方,它只是内容搬运工,对文件内容不做任何_CRUD_。

Stream依然不存储数据,不同的是它可以检索(Retrieve)和逻辑处理集合数据、包括筛选、排序、统计、计数等。可以想象成是 Sql 语句。

它的源数据可以是 CollectionArray 等。由于它的方法参数都是函数式接口类型,所以一般和 Lambda 配合使用。

5.Optional

Optional类是用于解决空指针异常的问题。它可以用来包装一个可能为null的值,并在值不存在时提供一些默认行为。

空指针异常是导致Java应用程序失败的最常见原因。以前,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类,Guava通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。受到Google Guava的启发,Optional类已经成为Java 8类库的一部分。

Optional 类(java.util.Optional) 是一个容器类, 它可以保存类型T的值,代表这个值存在。或者仅仅保存null,表示这个值不存在。原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。而且Optional提供很多有用的方法,这样我们就不用显式进行空值检测。

防止 NPE,是程序员的基本修养,注意 NPE 产生的场景:
1) 返回类型为基本数据类型,return 包装数据类型的对象时,自动拆箱有可能产生 NPE。
反例:public int f() { return Integer 对象}, 如果为 null,自动解箱抛 NPE。
2) 数据库的查询结果可能为 null。
3) 集合里的元素即使 isNotEmpty,取出的数据元素也可能为 null。
4) 远程调用返回对象时,一律要求进行空指针判断,防止 NPE。
5) 对于 Session 中获取的数据,建议进行 NPE 检查,避免空指针。
6) 级联调用 obj.getA().getB().getC();一连串调用,易产生 NPE。
正例:使用 JDK8 的 Optional 类来防止 NPE 问题。

建议使用 Optional 解决 NPE(java.lang.NullPointerException)问题,它就是为 NPE 而生的,其中可以包含空值或非空值。

6.Date-Time API

这是对java.util.Date强有力的补充,解决了 Date 类的大部分痛点:

  1. 非线程安全
  2. 时区处理麻烦
  3. 各种格式化、和时间计算繁琐
  4. 设计有缺陷,Date 类同时包含日期和时间;还有一个 java.sql.Date,容易混淆。

我们从常用的时间实例来对比 java.util.Date 和新 Date 有什么区别。用java.util.Date的代码该改改了。

java.time 主要类

java.util.Date 既包含日期又包含时间,而 java.time 把它们进行了分离

1
2
3
LocalDateTime.class //日期+时间 format: yyyy-MM-ddTHH:mm:ss.SSS
LocalDate.class //日期 format: yyyy-MM-dd
LocalTime.class //时间 format: HH:mm:ss

Java 17新特性

Java 17 在 2021 年 9 月 14 日正式发布,是一个长期支持(LTS)版本。

Spring 6.x 和 Spring Boot 3.x 最低支持的就是 Java 17。

switch 的类型匹配(预览)

switch 代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Old code
static String formatter(Object o) {
String formatted = "unknown";
if (o instanceof Integer i) {
formatted = String.format("int %d", i);
} else if (o instanceof Long l) {
formatted = String.format("long %d", l);
} else if (o instanceof Double d) {
formatted = String.format("double %f", d);
} else if (o instanceof String s) {
formatted = String.format("String %s", s);
}
return formatted;
}

// New code
static String formatterPatternSwitch(Object o) {
return switch (o) {
case Integer i -> String.format("int %d", i);
case Long l -> String.format("long %d", l);
case Double d -> String.format("double %f", d);
case String s -> String.format("String %s", s);
default -> o.toString();
};
}

对于 null 值的判断也进行了优化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Old code
static void testFooBar(String s) {
if (s == null) {
System.out.println("oops!");
return;
}
switch (s) {
case "Foo", "Bar" -> System.out.println("Great");
default -> System.out.println("Ok");
}
}

// New code
static void testFooBar(String s) {
switch (s) {
case null -> System.out.println("Oops");
case "Foo", "Bar" -> System.out.println("Great");
default -> System.out.println("Ok");
}
}

Java 21新特性

image.png

JDK 21 于 2023 年 9 月 19 日 发布,这是一个非常重要的版本,里程碑式。

JDK21 是 LTS(长期支持版),至此为止,目前有 JDK8、JDK11、JDK17 和 JDK21 这四个长期支持版了

虚拟线程

虚拟线程(Virtual Thread)是 JDK 而不是 OS 实现的轻量级线程(Lightweight Process,LWP),由 JVM 调度。许多虚拟线程共享同一个操作系统线程,虚拟线程的数量可以远大于操作系统线程的数量。

在引入虚拟线程之前,java.lang.Thread 包已经支持所谓的平台线程,也就是没有虚拟线程之前,我们一直使用的线程。JVM 调度程序通过平台线程(载体线程)来管理虚拟线程,一个平台线程可以在不同的时间执行不同的虚拟线程(多个虚拟线程挂载在一个平台线程上),当虚拟线程被阻塞或等待时,平台线程可以切换到执行另一个虚拟线程。

虚拟线程解决的问题

Java 虚拟线程解决了以下几个方面的问题:

  1. 线程资源开销大的问题
  • 内存占用方面:在传统的 Java 线程模型中,每个线程都需要分配大约 1MB 的内存作为堆栈存储空间。在大规模并发场景下,如果创建大量的线程,会占用大量的内存资源,可能导致内存不足的问题。而虚拟线程只需要很少的内存资源,单个 JVM 就可以轻松创建数百万个虚拟线程,极大地降低了内存占用。
  • 创建和销毁成本方面:创建传统线程的开销较大,涉及到操作系统层面的资源分配和初始化等操作。频繁地创建和销毁大量线程会消耗大量的 CPU 时间和系统资源。虚拟线程的创建和销毁成本非常低,这使得在需要大量并发任务的场景下,系统能够更高效地管理线程资源。
  1. 同步 IO 下线程池的阻塞排队问题
  • 在同步 IO 的情况下,使用传统线程池时,当线程都在执行阻塞 IO 操作时,会导致线程被挂起,其他后续的任务需要等待线程池中的线程完成 IO 操作并返回后才能继续执行,从而产生阻塞排队问题,增加了任务的延迟。而虚拟线程在调用阻塞 IO 操作时,虽然虚拟线程会被挂起,但是被挂起虚拟线程关联的操作系统线程可以为其他虚拟线程继续服务,从而避免了这种阻塞排队问题,提高了系统的并发处理能力和响应速度。
  1. 异步编程模型的复杂性问题
  • 异步编程通常依赖于回调函数,这种方式会导致代码的逻辑变得复杂,可读性和可维护性较差,并且容易出现回调地狱等问题,即多层嵌套的回调函数使得代码难以理解和调试。使用虚拟线程,开发者可以以同步的方式编写异步代码,由 JDK 负责线程的调度和切换,使得代码的编写更加符合人类的思维习惯,提高了代码的可读性和可维护性。
  1. 难以充分利用硬件资源的问题
  • 在高并发场景下,传统的线程模型可能无法充分利用硬件资源,导致硬件资源的利用率不高。虚拟线程的引入使得应用程序能够以接近最佳的硬件利用率进行扩展,即使在使用同步阻塞 API 的情况下,也能实现高性能、高吞吐量的应用程序,充分发挥硬件的性能。