JDK20 都来了,你还在用 JDK8 吗?
JDK8 引入了很多实用的新特性,JDK8 的这些新特性,极大地改进了Java 的开发效率和灵活性。
2014 -2023 年期间,Java 从 JDK8 到 JDK20 一共发布了13个版本,但 JDK8 依然是当下最受欢迎的,并且稳如泰山。
在面试中,面试官也特别喜欢问一些 JDK8 新特性的问题。
附图:skyn 发布的《JVM生态报告》
本文主要深入 JDK8 最具有代表性的5大新特性(面试高频必问),包括它们的用法及应用场景详解。
- Lambda 表达式和方法引用
- 接口中的默认方法和静态方法
- 函数式接口
- Stream API
- 时间日期API
1. Lambda表达式和方法引用
Lambda 表达式是 JDK8 中引入的一种新语法,也是一种匿名函数,它可以作为参数传递给其他方法或存储在变量中,让代码编写更加简洁、可读性高。
Lambda 表达式的语法很简洁,由一个参数列表、一个箭头和一个函数体组成。
例如:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
Collections.sort(names, (String a, String b) -> a.compareTo(b));
示例使用 Lambda 表达式作为 Collections.sort 方法的第二个参数。Lambda 表达式的函数体是 a.compareTo(b),它实现了比较两个字符串的逻辑。
方法引用则是对 Lambda 表达式的一种简化形式。
方法引用允许引用已有的方法作为 Lambda 表达式的函数体。例如:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
Collections.sort(names, String::compareTo);
这段代码与之前的代码是差不多的效果,但是使用了方法引用来代替 Lambda 表达式。
String::compareTo 是一个方法引用,它引用了 String 类中的 compareTo 方法。
2. 接口中的默认方法和静态方法
在 JDK8 之前,接口中只能声明抽象方法。
JDK8 引入了默认方法和静态方法之后,可以更加便捷高效在接口中添加新的功能。
默认方法:
默认方法是一种在接口中有默认实现的方法,能够在不改变现有实现的情况下添加新功能,默认方法可以通过关键字 default 来声明。
例如:
public interface Vehicle {
default void move() {
System.out.println("Vehicle is moving");
}
}
这个接口声明了一个默认方法 move(),它打印出“Vehicle is moving”这句话。如果某个类实现了这个接口但没有重写 move() 方法,那么默认实现就会被使用。
默认方法可以用于向现有接口添加新的功能,而不会影响现有的实现。如果我们需要在现有接口上添加新功能,这种功能就非常有用了。
静态方法:
静态方法是一种在接口中声明的静态方法,能够通过接口名直接调用,静态方法可以通过关键字 static 来声明。
例如:
public interface Vehicle {
static void honk() {
System.out.println("Vehicle is honking");
}
}
这个接口声明了一个静态方法 honk(),它打印出“Vehicle is honking”这句话。
在调用这个方法时,可以使用接口名来调用:
Vehicle.honk();
静态方法可以用于在接口中声明一些通用的方法,这些方法可以被接口的所有实现类所共享。
3. 函数式接口
在 Java 8 中,Java 内置了很多函数式接口。
最常用的函数式接口:
- Predicate<T>:判断给定输入值是否符合条件,返回一个布尔值。
- Function<T, R>:将输入类型 T 的对象转换为输出类型 R 的对象。
- Supplier<T>:生成给定类型 T 的对象。
- Consumer<T>:表示接受单个输入参数,并且不返回任何结果的操作。
函数式接口是只有一个抽象方法的接口,它可以被 Lambda 表达式所赋值,在任何需要函数式编程的场景中使用。
我们通常使用函数式接口来实现复杂的逻辑,例如:多线程编程、事件驱动编程和响应式编程等场景。
import java.util.function.Predicate;
public class Example {
public static void main(String[] args) {
Predicate<String> predicate = (s) -> s.length() > 0;
System.out.println(predicate.test("foo")); // true
System.out.println(predicate.negate().test("foo")); // false
Predicate<String> isEmpty = String::isEmpty;
System.out.println(isEmpty.test("")); // true
System.out.println(isEmpty.negate().test("")); // false
}
}
在这个例子中:
使用了 Predicate 接口,Lambda 表达式 (s) -> s.length() > 0 作为 Predicate 的实现,用于判断字符串是否为空。
展示了如何使用 negate() 方法来返回 Predicate 实例的逆。
使用了方法引用 String::isEmpty,这是一个函数式接口的实现,表示一个字符串是否为空。
4. Stream API
Java 对函数式编程的重视程度,看看 JDK8 加入函数式编程扩充多少功能就明白了。
JDK8 引入函数式编程,主要原因:
- 使用函数式编程写出的代码简洁、可读性高,使用 stream 接口,让你从此告别 for 循环。
- 使用函数式编程编写并行程序非常简单,只需要调用一下 parallel() 方法就搞定了。
说到这里,就不得不提 Stream,也就是 Java 函数式编程的主角。
Stream API 是一种基于流的编程模型,可以简化集合、数组等数据处理的操作,提高数据处理的效率。它包含了筛选、映射、排序等多种操作,这些操作可以进行链式调用,最终返回一个结果。
Stream API 的应用场景非常广泛,特别是在大数据处理方面。
Stream API 的使用方法:
import java.util.Arrays;
import java.util.List;
public class StreamDemo {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
int sum = numbers.stream() // 创建Stream对象
.filter(n -> n % 2 == 0) // 筛选出偶数
.mapToInt(n -> n * 2) // 将每个元素乘以2
.sum(); // 对结果进行求和
System.out.println(sum); // 输出结果20
}
}
这个示例中:
我们首先创建了一个包含 10 个整数的 List 对象 numbers,再通过 numbers.stream() 方法创建了一个Stream对象,并进行了filter()、mapToInt()等一系列的中间操作,这些操作会返回一个新的 Stream 对象。
最终,我们调用了 sum() 方法对结果进行求和,得到了结果 20 。
5. 时间日期API
在 JDK8 之前,处理时间日期 API 非常不方便。
JDK8 中引入了新的时间日期 API ,提供了更加简洁、易用和安全的方式来处理日期。
新的时间日期 API 包含了多种类和接口,例如:LocalDate、LocalTime、LocalDateTime 等。
import java.time.LocalDate;
import java.time.Month;
public class DateDemo {
public static void main(String[] args) {
// 创建LocalDate对象
LocalDate date = LocalDate.of(2022, Month.MAY, 1);
System
5.1 LocalDate、LocalTime 及 LocalDateTime
新的时间日期 API 引入了一些新的类:
- LocalDate 用于表示日期
- LocalTime 用于表示时间
- LocalDateTime 同时表示日期和时间
这些类的实例可以通过静态工厂方法来创建。
例如:要创建当前日期的实例,可以使用 LocalDate.now() 方法,而要创建指定日期的实例,可以使用 LocalDate.of() 方法。
// 创建当前日期的实例
LocalDate today = LocalDate.now();
// 创建指定日期的实例
LocalDate date = LocalDate.of(2023, 4, 25);
5.2 Instant
Instant 是用于表示时间戳的类,主要用于计算两个时间点之间的时间间隔,它可以精确到纳秒级别。
Instant 类同样也提供了静态工厂方法来创建实例。
例如:
创建当前时间的实例,使用 Instant.now() 方法。
// 创建当前时间的实例
Instant now = Instant.now();
创建指定时间的实例,使用 Instant.ofEpochSecond() 方法。
// 创建指定时间的实例
Instant instant = Instant.ofEpochSecond(1619289680);
5.3 Duration 和 Period
Duration 表示两个时间点之间的时间间隔,Period 表示两个日期之间的时间间隔,这两个类同样也提供了静态工厂方法来创建实例。
例如:
创建一个持续 1 小时的 Duration 实例,使用 Duration.ofHours() 方法。
// 创建一个持续1小时的Duration实例
Duration duration = Duration.ofHours(1);
创建一个持续3天的Period实例,可以使用Period.ofDays()方法。
//
创建一个持续3天的Period实例 Period period = Period.ofDays(3);
5.4 DateTimeFormatter
DateTimeFormatter 是将日期时间对象格式化为字符串的类,它支持各种日期时间格式,可以自定义格式。
例如:将当前日期格式化为"yyyy-MM-dd"格式的字符串
// 将当前日期格式化为"yyyy-MM-dd"格式的字符串
String formattedDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
5.5 时区支持
JDK8 中,新的时间日期 API,提供了更加简洁、易用和安全的方式来处理日期。
新的时间日期 API 提供了对时区的支持,引入了 ZoneId 和 ZoneOffset 类来表示时区信息,并且提供了静态工厂方法来创建实例。
例如:使用ZoneId.of()方法,来创建一个表示东京时区。
// 创建一个表示东京时区的实例
ZoneId tokyoZone = ZoneId.of("Asia/Tokyo");
总结
以上,是 JDK8 最具价值的 5 个新特性的全部介绍。
关于 JDK 版本选择,宝妹儿个人的看法是:将实用性、稳定性放在第一位。
IT 江湖上曾经流传的一句老话:一个功能只要能正常工作,如非必要就不要动它。
谢谢关注 Java面试题宝,我是爱分享的程序员宝妹儿。
如果觉得不错,请一键三连支持下,奉上我最新整理的 JDK8 面试题及答案,可以参考学习备面、复盘本篇知识,都是互联网中大厂面试最爱问、也很重要的知识点。