
Java基础:Java 8-21新特性核心知识体系(面试版)
一、整体演进与核心价值
Java 8是Java语言发展史上的里程碑,引入了函数式编程范式,彻底改变了Java代码的编写风格。后续Java 9-21版本在Java 8基础上持续优化和增强,使函数式编程更加完善、高效。
核心价值:
- 代码更简洁、可读性更高
- 支持并行计算,充分利用多核CPU
- 减少空指针异常(Optional)
- 提供声明式编程风格,专注于"做什么"而非"怎么做"
二、Lambda表达式
2.1 定义与本质
Lambda表达式是可传递的匿名函数,本质是函数式接口的实例。它没有名称,但有参数列表、函数体和返回类型。
2.2 语法格式
(参数列表) -> {
函数体 }
语法简化规则:
- 参数类型可省略,编译器自动推断
- 单个参数时,括号可省略
- 函数体只有一条语句时,大括号和return可省略
示例:
// 完整写法
Comparator<Integer> comparator1 = (Integer a, Integer b) -> {
return a.compareTo(b); };
// 简化写法
Comparator<Integer> comparator2 = (a, b) -> a.compareTo(b);
2.3 变量捕获
- Lambda可以访问外部局部变量,但该变量必须是final或事实上的final(Java 8+)
- Lambda可以访问实例变量和静态变量
- Lambda内部不能修改局部变量的值
示例:
int num = 10; // 事实上的final
Runnable runnable = () -> System.out.println(num);
// num = 20; // 编译错误,不能修改
2.4 方法引用与构造器引用
方法引用是Lambda表达式的进一步简化,当Lambda体只调用一个方法时使用。
| 类型 | 语法 | 示例 |
|---|---|---|
| 静态方法引用 | 类名::静态方法名 | Integer::parseInt |
| 实例方法引用 | 对象::实例方法名 | System.out::println |
| 类名::实例方法名 | 类名::实例方法名 | String::compareTo |
| 构造器引用 | 类名::new | ArrayList::new |
2.5 底层原理
- Lambda表达式在编译时会生成私有静态方法
- 运行时通过
invokedynamic指令动态调用 - 不会生成匿名内部类的class文件,性能更高
三、函数式接口
3.1 定义
只有一个抽象方法的接口称为函数式接口,可以用@FunctionalInterface注解标记(可选,但推荐使用,编译器会检查)。
3.2 四大核心函数式接口
这是Java 8提供的最基础、最常用的函数式接口,位于java.util.function包。
| 接口 | 抽象方法 | 作用 | 泛型说明 |
|---|---|---|---|
Consumer<T> |
void accept(T t) |
消费型接口:接收一个参数,无返回值 | T:输入参数类型 |
Supplier<T> |
T get() |
供给型接口:无参数,返回一个值 | T:返回值类型 |
Function<T, R> |
R apply(T t) |
函数型接口:接收一个参数,返回一个值 | T:输入参数类型,R:返回值类型 |
Predicate<T> |
boolean test(T t) |
断言型接口:接收一个参数,返回布尔值 | T:输入参数类型 |
3.3 扩展函数式接口
为了处理基本类型(避免自动装箱拆箱的性能损耗)和多参数场景,Java提供了大量扩展接口:
- 基本类型特化:
IntConsumer、LongSupplier、DoubleFunction等 - 二元操作:
BiConsumer<T, U>、BiFunction<T, U, R>、BiPredicate<T, U> - 一元操作:
UnaryOperator<T>(Function的子接口,输入输出类型相同) - 二元操作:
BinaryOperator<T>(BiFunction的子接口,输入输出类型相同)
3.4 自定义函数式接口
@FunctionalInterface
public interface MyFunction<T, R> {
R apply(T t);
// 可以有默认方法和静态方法
default void defaultMethod() {
}
static void staticMethod() {
}
}
四、Stream流
4.1 定义与特点
Stream是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
核心特点:
- 不存储数据,只是对数据进行计算
- 不改变源数据,会生成一个新的流
- 惰性求值:只有调用终止操作时,中间操作才会执行
- 一次性消费:流只能使用一次,使用后会关闭
4.2 流的操作流程
数据源 -> 中间操作1 -> 中间操作2 -> ... -> 终止操作
4.3 流的创建
// 1. 集合创建(最常用)
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream1 = list.stream(); // 串行流
Stream<String> parallelStream1 = list.parallelStream(); // 并行流
// 2. 数组创建
String[] array = {
"a", "b", "c"};
Stream<String> stream2 = Arrays.stream(array);
// 3. 值创建
Stream<String> stream3 = Stream.of("a", "b", "c");
// 4. 无限流
Stream<Integer> stream4 = Stream.iterate(0, n -> n + 2); // 迭代
Stream<Double> stream5 = Stream.generate(Math::random); // 生成
4.4 中间操作
中间操作返回一个新的流,可以链式调用。
4.4.1 筛选与切片
| 方法 | 作用 |
|---|---|
filter(Predicate<T>) |
过滤流中满足条件的元素 |
distinct() |
去重(根据equals()方法) |
limit(long n) |
截断流,只保留前n个元素 |
skip(long n) |
跳过前n个元素 |
4.4.2 映射
| 方法 | 作用 |
|---|---|
map(Function<T, R>) |
将流中的每个元素映射成另一个元素 |
flatMap(Function<T, Stream<R>>) |
将流中的每个元素映射成一个流,然后将所有流连接成一个流 |
map与flatMap的区别:
- map:一对一映射
- flatMap:一对多映射,会"扁平化"流
4.4.3 排序
| 方法 | 作用 |
|---|---|
sorted() |
自然排序(元素必须实现Comparable接口) |
sorted(Comparator<T>) |
定制排序 |
4.5 终止操作
终止操作会触发流的计算,计算完成后流就关闭了。
4.5.1 匹配与查找
| 方法 | 作用 | 返回值 |
|---|---|---|
allMatch(Predicate<T>) |
检查是否所有元素都满足条件 | boolean |
anyMatch(Predicate<T>) |
检查是否至少有一个元素满足条件 | boolean |
noneMatch(Predicate<T>) |
检查是否没有元素满足条件 | boolean |
findFirst() |
返回第一个元素 | Optional |
findAny() |
返回任意一个元素(并行流中效率更高) | Optional |
4.5.2 归约
| 方法 | 作用 |
|---|---|
reduce(T identity, BinaryOperator<T>) |
从初始值开始,将流中元素累加 |
reduce(BinaryOperator<T>) |
没有初始值,返回Optional |
示例:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
int sum = list.stream().reduce(0, Integer::sum); // 15
Optional<Integer> max = list.stream().reduce(Integer::max); // Optional[5]
4.5.3 收集
collect(Collector<T, A, R>)是最常用的终止操作,将流转换为其他数据结构。
常用Collector:
// 转换为集合
List<String> list = stream.collect(Collectors.toList());
Set<String> set = stream.collect(Collectors.toSet());
Map<String, Integer> map = stream.collect(Collectors.toMap(String::length, Function.identity()));
// 分组
Map<Integer, List<String>> groupByLength = stream.collect(Collectors.groupingBy(String::length));
// 分区
Map<Boolean, List<String>> partitionByLength = stream.collect(Collectors.partitioningBy(s -> s.length() > 3));
// 聚合
long count = stream.collect(Collectors.counting());
Optional<String> max = stream.collect(Collectors.maxBy(String::compareTo));
String joining = stream.collect(Collectors.joining(", "));
4.6 并行流
- 并行流使用
Fork/Join框架,将任务分解成多个子任务并行执行 - 创建方式:
list.parallelStream()或stream.parallel() - 注意事项:
- 确保线程安全
- 避免在并行流中使用有状态的中间操作
- 数据量小时,并行流可能比串行流慢(线程切换开销)
4.7 Java 9-21 Stream增强
- Java 9:
takeWhile()、dropWhile()、ofNullable()、iterate()重载方法 - Java 10:
Collectors.toUnmodifiableList()、toUnmodifiableSet()、toUnmodifiableMap() - Java 12:
Collectors.teeing()(合并两个收集器的结果) - Java 16:
Stream.toList()(简化集合转换,返回不可变列表)
五、Optional
5.1 定义与作用
Optional是一个容器类,用于包装可能为null的值,从根本上解决空指针异常(NPE)问题。
5.2 核心方法
5.2.1 创建Optional
| 方法 | 作用 | 注意事项 |
|---|---|---|
Optional.of(T value) |
创建一个包含非null值的Optional | 如果value为null,抛出NPE |
Optional.ofNullable(T value) |
创建一个可能包含null值的Optional | 推荐使用 |
Optional.empty() |
创建一个空的Optional |
5.2.2 获取值
| 方法 | 作用 | 注意事项 |
|---|---|---|
get() |
获取Optional中的值 | 如果为空,抛出NoSuchElementException |
orElse(T other) |
如果有值则返回,否则返回other | 无论是否有值,other都会执行 |
orElseGet(Supplier<T> other) |
如果有值则返回,否则返回other.get() | 只有为空时,other才会执行 |
orElseThrow(Supplier<X> exceptionSupplier) |
如果有值则返回,否则抛出指定异常 | 推荐使用 |
5.2.3 判断与转换
| 方法 | 作用 |
|---|---|
isPresent() |
判断是否有值 |
ifPresent(Consumer<T> consumer) |
如果有值,执行consumer |
map(Function<T, R> mapper) |
如果有值,执行mapper并返回新的Optional |
flatMap(Function<T, Optional<R>> mapper) |
与map类似,但mapper返回Optional |
filter(Predicate<T> predicate) |
如果有值且满足条件,返回当前Optional,否则返回空 |
5.3 最佳实践
✅ 推荐:
// 链式调用
String name = user.getAddress()
.flatMap(Address::getCity)
.map(City::getName)
.orElse("未知");
// 存在时执行操作
user.ifPresent(u -> System.out.println(u.getName()));
// 抛出指定异常
User user = userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException("用户不存在"));
❌ 避免:
// 仍然会产生NPE
Optional<User> user = Optional.ofNullable(null);
user.get().getName();
// 没有解决根本问题
if (user.isPresent()) {
System.out.println(user.get().getName());
}
5.4 Java 9-21 Optional增强
- Java 9:
ifPresentOrElse()、or()、stream() - Java 10:
orElseThrow()(无参,抛出NoSuchElementException) - Java 11:
isEmpty()(与isPresent()相反)
六、Java 9-21其他重要新特性
6.1 Java 9
- 模块系统(JPMS)
- 接口私有方法
- 集合工厂方法:
List.of()、Set.of()、Map.of() - 改进的try-with-resources
6.2 Java 10
- 局部变量类型推断(var)
- 不可变集合增强
6.3 Java 11
- 字符串增强:
isBlank()、lines()、strip()等 - HttpClient API正式版
- 运行单个Java文件:
java HelloWorld.java
6.4 Java 14
- 记录类(Record)
- 模式匹配(instanceof)
- 文本块(预览)
6.5 Java 15
- 文本块正式版
- 密封类(预览)
6.6 Java 16
- 记录类正式版
- 模式匹配(instanceof)正式版
- 密封类第二次预览
6.7 Java 17(LTS)
- 密封类正式版
- 增强的伪随机数生成器
- 移除实验性的AOT和JIT编译器
6.8 Java 21(LTS)
- 虚拟线程(Project Loom)
- 模式匹配(switch)正式版
- 记录模式
- 字符串模板(预览)
七、面试核心考点与易错点
Lambda表达式的底层原理是什么?与匿名内部类有什么区别?
- 底层使用
invokedynamic指令,不会生成匿名内部类的class文件 - 变量捕获规则不同:Lambda要求局部变量是final或事实上的final
- 性能更高,内存占用更少
- 底层使用
函数式接口的定义是什么?为什么只能有一个抽象方法?
- 只有一个抽象方法的接口
- 因为Lambda表达式只能匹配一个抽象方法的签名
Stream流的中间操作和终止操作有什么区别?
- 中间操作返回新的流,可以链式调用,惰性求值
- 终止操作触发计算,计算完成后流关闭,返回非流类型
map和flatMap的区别是什么?
- map:一对一映射,返回普通对象
- flatMap:一对多映射,返回流,会"扁平化"流
并行流的底层原理是什么?使用时需要注意什么?
- 底层使用Fork/Join框架
- 注意线程安全,避免有状态的中间操作
- 数据量小时不建议使用,线程切换开销大于并行收益
Optional的正确使用方式是什么?
- 不要用Optional作为方法参数
- 不要用Optional作为类的字段
- 不要调用
get()方法,除非你确定Optional不为空 - 优先使用
orElseGet()、orElseThrow()和链式调用
八、综合实战案例
// 需求:从用户列表中筛选出年龄大于18岁的男性用户,按年龄排序,取前10个,获取他们的姓名列表
List<String> names = userList.stream()
.filter(user -> user.getAge() > 18)
.filter(user -> "男".equals(user.getGender()))
.sorted(Comparator.comparingInt(User::getAge))
.limit(10)
.map(User::getName)
.collect(Collectors.toList());
// 需求:计算所有用户的平均年龄
double averageAge = userList.stream()
.mapToInt(User::getAge)
.average()
.orElse(0.0);
// 需求:按性别分组,统计每个性别的用户数量
Map<String, Long> countByGender = userList.stream()
.collect(Collectors.groupingBy(User::getGender, Collectors.counting()));
Java 8-21核心新特性面试问答清单(可直接背诵版)
标注说明:★★★=高频必问,★★☆=中频常问,★☆☆=低频了解;⚠️=易错点
一、整体演进与核心价值
Q1:为什么说Java 8是Java发展史上的里程碑?(★★☆)
答:Java 8首次引入函数式编程范式,彻底改变了Java代码的编写风格,解决了传统面向对象代码冗长、并行计算困难、空指针异常频发等问题。后续Java 9-21版本均在Java 8基础上持续优化函数式编程能力。
核心价值:
- 代码更简洁、可读性更高
- 原生支持并行计算,充分利用多核CPU
- 从设计层面减少空指针异常(Optional)
- 提供声明式编程风格,专注"做什么"而非"怎么做"
二、Lambda表达式
Q2:Lambda表达式的本质是什么?语法格式是什么?(★★★)
答:Lambda表达式是可传递的匿名函数,本质是函数式接口的实例。
语法格式:(参数列表) -> { 函数体 }
语法简化规则:
- 参数类型可省略,编译器自动类型推断
- 单个参数时,括号可省略
- 函数体只有一条语句时,大括号和
return可同时省略
示例:
// 完整写法
Comparator<Integer> c1=(Integer a, Integer b)->{
return a.compareTo(b);};
// 简化写法
Comparator<Integer> c2=(a,b)->a.compareTo(b);
Q3:Lambda表达式的变量捕获规则是什么?(★★★)
答:
- 可以访问实例变量和静态变量,且可修改
- 可以访问外部局部变量,但该变量必须是final或事实上的final(Java 8+)
- Lambda内部不能修改局部变量的值
⚠️ 易错点:即使没有显式声明final,只要局部变量在Lambda之后没有被修改,就是"事实上的final";一旦修改,编译报错。
Q4:方法引用有哪几种类型?分别举个例子。(★★★)
答:方法引用是Lambda表达式的简化形式,当Lambda体只调用一个方法时使用,共4种类型:
| 类型 | 语法 | 示例 | 适用场景 |
|---|---|---|---|
| 静态方法引用 | 类名::静态方法名 | Integer::parseInt |
调用类的静态方法 |
| 实例方法引用 | 对象::实例方法名 | System.out::println |
调用已有对象的实例方法 |
| 类名::实例方法名 | 类名::实例方法名 | String::compareTo |
第一个参数作为方法调用者 |
| 构造器引用 | 类名::new | ArrayList::new |
调用构造器创建对象 |
Q5:Lambda表达式与匿名内部类有什么区别?(★★★)
答:
- 本质不同:Lambda是函数式接口的实例;匿名内部类是类的实例
- 编译方式不同:Lambda编译时生成私有静态方法,通过
invokedynamic指令调用;匿名内部类生成单独的类名$1.class文件 - 变量捕获不同:Lambda要求局部变量是final/事实上的final;匿名内部类可以修改非final局部变量(Java 8之前)
- this指向不同:Lambda的
this指向外部类;匿名内部类的this指向自身 - 性能不同:Lambda没有类加载开销,性能更高,内存占用更少
Q6:Lambda表达式的底层原理是什么?(★★☆)
答:
- 编译时:编译器为Lambda表达式生成一个私有静态方法,方法体就是Lambda的代码
- 运行时:通过
invokedynamic指令动态调用该方法,由JVM在运行时生成函数式接口的实例 - 优势:避免了匿名内部类的类加载和对象创建开销,性能更优
三、函数式接口
Q7:什么是函数式接口?@FunctionalInterface注解的作用是什么?(★★★)
答:只有一个抽象方法的接口称为函数式接口。
@FunctionalInterface注解的作用:
- 编译器检查:确保接口只有一个抽象方法,否则编译报错
- 文档说明:明确该接口是为函数式编程设计的
⚠️ 易错点:函数式接口可以有默认方法和静态方法,因为它们不是抽象方法。
Q8:Java 8提供的四大核心函数式接口是什么?分别说明其作用。(★★★)
答:四大核心接口位于java.util.function包,是所有函数式编程的基础:
| 接口 | 抽象方法 | 作用 | 泛型说明 |
|---|---|---|---|
Consumer<T> |
void accept(T t) |
消费型接口:接收一个参数,无返回值 | T:输入参数类型 |
Supplier<T> |
T get() |
供给型接口:无参数,返回一个值 | T:返回值类型 |
Function<T, R> |
R apply(T t) |
函数型接口:接收一个参数,返回一个值 | T:输入,R:输出 |
Predicate<T> |
boolean test(T t) |
断言型接口:接收一个参数,返回布尔值 | T:输入参数类型 |
Q9:为什么函数式接口只能有一个抽象方法?(★★☆)
答:因为Lambda表达式只能匹配一个方法签名。如果接口有多个抽象方法,Lambda无法确定要实现哪个方法,编译器会报错。
Q10:什么是基本类型特化的函数式接口?为什么需要它们?(★★☆)
答:为了避免自动装箱拆箱的性能损耗,Java为基本类型提供了特化的函数式接口,例如:
IntConsumer、LongConsumer、DoubleConsumer(消费型)IntSupplier、LongSupplier、DoubleSupplier(供给型)IntFunction<R>、LongFunction<R>、DoubleFunction<R>(函数型)
⚠️ 性能优化点:处理大量基本类型数据时,优先使用特化接口,避免频繁装箱拆箱。
四、Stream流
Q11:Stream流的定义和核心特点是什么?(★★★)
答:Stream是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
核心特点:
- 不存储数据:只是对数据进行计算,不保存元素
- 不改变源数据:所有操作都会生成一个新的流
- 惰性求值:只有调用终止操作时,中间操作才会执行
- 一次性消费:流只能使用一次,使用后会自动关闭
Q12:Stream流的操作流程是什么?中间操作和终止操作有什么区别?(★★★)
答:操作流程:数据源 -> 中间操作1 -> 中间操作2 -> ... -> 终止操作
| 区别 | 中间操作 | 终止操作 |
|---|---|---|
| 返回值 | 返回新的Stream流 | 返回非流类型(集合、数值、布尔值等) |
| 执行时机 | 惰性求值,不立即执行 | 立即执行,触发所有中间操作 |
| 链式调用 | 可以链式调用多个 | 只能有一个,是流的最后一步 |
| 流状态 | 流仍然可用 | 流关闭,不可再用 |
Q13:map()和flatMap()有什么区别?(★★★)
答:
- map():一对一映射,将流中的每个元素转换为另一个元素,返回
Stream<R> - flatMap():一对多映射,将流中的每个元素转换为一个流,然后将所有流"扁平化"为一个流,返回
Stream<R>
示例:
List<String> words=Arrays.asList("hello","world");
// map:返回Stream<Stream<Character>>
Stream<Stream<Character>> stream1=words.stream().map(word->word.chars().mapToObj(c->(char)c));
// flatMap:返回Stream<Character>
Stream<Character> stream2=words.stream().flatMap(word->word.chars().mapToObj(c->(char)c));
Q14:reduce()和collect()有什么区别?(★★☆)
答:两者都是终止操作,用于将流中的元素聚合为一个结果:
- reduce():不可变聚合,将流中的元素逐个累加,最终返回一个值
- collect():可变聚合,将流中的元素收集到一个可变容器(如List、Set、Map)中
适用场景:
- reduce:适合简单的数值计算(求和、求最大值、求最小值)
- collect:适合复杂的集合转换(分组、分区、拼接字符串)
Q15:并行流的底层原理是什么?使用时需要注意什么?(★★★)
答:
底层原理:并行流基于Fork/Join框架实现,将大任务拆分成多个小任务,分配给多个线程并行执行,最后合并结果。
使用注意事项:
- 线程安全:避免在并行流中修改共享变量
- 无状态操作:中间操作必须是无状态的,否则会导致结果不确定
- 数据量:数据量小时不建议使用,线程切换开销大于并行收益
- 避免阻塞:不要在并行流中执行阻塞操作(如IO操作)
Q16:Java 9-21对Stream流有哪些重要增强?(★★☆)
答:
- Java 9:
takeWhile()(取满足条件的元素直到不满足)、dropWhile()(跳过满足条件的元素直到不满足)、ofNullable()(创建包含单个元素或空的流)、iterate()重载方法(支持终止条件) - Java 10:
Collectors.toUnmodifiableList()/toUnmodifiableSet()/toUnmodifiableMap()(返回不可变集合) - Java 12:
Collectors.teeing()(合并两个收集器的结果) - Java 16:
Stream.toList()(简化集合转换,返回不可变列表,推荐替代Collectors.toList())
五、Optional
Q17:Optional的设计思想和作用是什么?(★★★)
答:Optional是一个容器类,用于包装可能为null的值。它的设计思想是强制开发者显式处理空值情况,从根本上避免空指针异常(NPE)。
Q18:创建Optional的三种方法是什么?有什么区别?(★★★)
答:
Optional.of(T value):创建包含非null值的Optional- ⚠️ 如果value为null,直接抛出NPE
Optional.ofNullable(T value):创建可能包含null值的Optional- ✅ 推荐使用,无论value是否为null都不会抛出异常
Optional.empty():创建一个空的Optional
Q19:获取Optional值的四种方法是什么?有什么区别?(★★★)
答:
| 方法 | 作用 | 空值处理 | 执行时机 | 推荐度 |
|---|---|---|---|---|
get() |
获取Optional中的值 | 抛出NoSuchElementException |
- | ❌ 不推荐 |
orElse(T other) |
有值返回,否则返回other | 返回默认值 | 无论是否有值,other都会执行 | ⚠️ 谨慎使用 |
orElseGet(Supplier<T> other) |
有值返回,否则返回other.get() | 返回默认值 | 只有为空时才执行 | ✅ 推荐 |
orElseThrow(Supplier<X> exception) |
有值返回,否则抛出指定异常 | 抛出业务异常 | 只有为空时才执行 | ✅ 强烈推荐 |
⚠️ 易错点:orElse()即使Optional有值,也会执行括号内的代码(如创建对象、查询数据库),造成不必要的性能损耗。
Q20:Optional的map()和flatMap()有什么区别?(★★☆)
答:
map(Function<T, R> mapper):如果有值,执行mapper并返回Optional<R>flatMap(Function<T, Optional<R>> mapper):如果有值,执行mapper并直接返回Optional<R>(不会嵌套)
示例:
// map:返回Optional<Optional<String>>
Optional<Optional<String>> city1=user.map(User::getAddress).map(Address::getCity);
// flatMap:返回Optional<String>
Optional<String> city2=user.flatMap(User::getAddress).flatMap(Address::getCity);
Q21:Optional的最佳实践和常见错误是什么?(★★★)
✅ 最佳实践:
- 使用链式调用处理多层嵌套的空值
- 使用
ifPresent()执行存在时的操作 - 使用
orElseThrow()抛出业务异常 - 使用
orElseGet()获取默认值
❌ 常见错误:
- 用
isPresent()判断后调用get()(和直接判空没有区别) - 用Optional作为方法参数或类的字段
- 调用
get()方法(除非100%确定Optional不为空) - 用
Optional.of(null)(会直接抛出NPE)
Q22:Java 9-21对Optional有哪些重要增强?(★★☆)
答:
- Java 9:
ifPresentOrElse()(有值执行第一个操作,否则执行第二个)、or()(为空时返回另一个Optional)、stream()(将Optional转换为Stream) - Java 10:
orElseThrow()(无参,空时抛出NoSuchElementException) - Java 11:
isEmpty()(判断是否为空,与isPresent()相反)
六、综合高频考点
Q23:Stream和集合有什么区别?(★★☆)
答:
- 存储方式:集合存储数据;Stream不存储数据,只进行计算
- 修改方式:集合可以添加、删除元素;Stream不改变源数据
- 执行方式:集合是立即执行;Stream是惰性求值
- 遍历次数:集合可以多次遍历;Stream只能遍历一次
- 并行支持:集合需要手动实现并行;Stream原生支持并行
Q24:如何避免Stream流的性能问题?(★★☆)
答:
- 优先使用基本类型特化流(
IntStream、LongStream、DoubleStream),避免装箱拆箱 - 数据量小时使用串行流,数据量大时使用并行流
- 避免在中间操作中执行耗时操作
- 合理使用
limit()和skip()减少数据处理量 - 避免使用有状态的中间操作(如
distinct()、sorted()在并行流中性能较差)
七、易错点终极汇总
- Lambda表达式中不能修改局部变量的值
- Stream流只能使用一次,使用后会关闭
orElse()无论是否有值都会执行,orElseGet()只有为空时才执行- 并行流中使用有状态操作会导致结果不确定
- 不要用
Optional.of(null),应该用Optional.ofNullable(null) - 不要用
isPresent()+get(),应该用链式调用 - 函数式接口只能有一个抽象方法,但可以有多个默认方法和静态方法