1.你知道 Java 8 有什么新特性吗?
lambda 表达式,stream 流,异步调用 接口可以写默认实现
Java 8 是 Java 历史上一个非常重要的版本,引入了很多革新特性。最核心的有以下几点:
- Lambda 表达式:它极大地简化了匿名内部类的写法,让我们能用函数式编程的思想来写代码,代码更紧凑。
// 旧写法
new Thread(new Runnable() {
@Override
public void run() { System.out.println("Old"); }
}).start();
// Lambda 写法
new Thread(() -> System.out.println("New")).start();-
Stream API:这是我平时用得最多的。它让集合的操作变得像写 SQL 一样流畅,支持链式处理,比如 filter 过滤、map 转换,还能通过 parallelStream 轻松利用多核 CPU 进行并行计算。
-
Optional:用来优雅地解决空指针异常,避免了代码里到处写
if (obj != null)。 -
CompletableFuture:提供了强大的异步编程能力,支持任务编排和回调,比以前的 Future 好用很多。
-
接口默认方法:允许在接口里写默认实现,这增强了接口的扩展性。
-
还有像新的日期时间 API,它是线程安全的,解决了旧版 Date 的很多坑。
2.Lambda 表达式了解吗?
1. 什么是 Lambda 表达式?
一句话总结: Lambda 表达式本质上是一个 “匿名函数”(没有名字的函数)。
-
核心目的: 它可以把“一段代码”像数据一样传递给方法。
-
直观感受: 它是对 匿名内部类 的“语法糖”优化,让代码变得极其简洁。
2. 为什么要用它?(核心优势)
-
代码简洁: 告别了啰嗦的
new Runnable() { ... }这种样板代码。 -
行为参数化: 可以把“逻辑”当作参数传进方法里(比如传一个排序逻辑给
sort方法)。 -
支持并行处理: 它是 Java 8 另一大杀器 Stream API 的基础。
3. 使用前提:函数式接口 (Functional Interface)
面试必考点: Lambda 表达式只能用于实现 “函数式接口”。
-
定义: 只有一个抽象方法的接口。
-
注解: 通常用
@FunctionalInterface标记(不是必须的,但加上了编译器会帮你检查)。 -
常见例子:
Runnable、Callable、Comparator。
| 接口名 | 抽象方法 | 参数 | 返回值 | 作用描述 | 记忆口诀 |
|---|---|---|---|---|---|
Consumer<T> | void accept(T t) | 有 | 无 | 消费型: 给你个东西,你把它吃了(打印、写入数据库)。 | 只进不出 |
Supplier<T> | T get() | 无 | 有 | 供给型: 没参数,你得给我变个东西出来(工厂方法)。 | 只出不进 |
Function<T, R> | R apply(T t) | 有 | 有 | 函数型: 给你 T,你把它变成 R(数据转换,如 String 转 int)。 | 有进有出 |
Predicate<T> | boolean test(T t) | 有 | boolean | 断言型: 给你个东西,你告诉我它是对是错(过滤数据)。 | 判官 |
- 调试困难
3.Java中stream的API介绍一下
如果说 Collection(集合) 是用来存数据的,那么 Stream(流) 就是用来算数据的。
1. 什么是 Stream 流?
一句话总结: Stream 就像一条**“流水线”。 它不是数据结构,不保存数据,而是负责对数据进行加工处理**(过滤、映射、排序等)。
-
特性:
-
不存储数据: 它只是搬运工,数据源还是 List 或 Set。
-
不改变源数据: 所有的操作(比如过滤)都会产生一个新的流,原集合不受影响。
-
延迟执行 (Lazy): 只有当你要结果(调用终端操作)的时候,流水线才会真正开始转动。
-
一次性消耗: 流水线走完了就没了,不能重复使用(想再操作必须重新生成一个流)。
-
2. Stream 的操作流程 (三步走)
面试官常问:“怎么用 Stream?” 回答这三步即可:
第一步:创建流 (Source)
-
从集合创建:
list.stream() -
从数组创建:
Arrays.stream(arr) -
直接创建:
Stream.of("a", "b", "c")
第二步:中间操作 (Intermediate Operations)
-
特点: 返回值依然是 Stream,可以链式调用。它是懒加载的,只有最后一步触发时才会执行。
-
常用方法:
-
filter(Predicate):过滤(挑出符合条件的)。 -
map(Function):转换(把苹果变成苹果汁,把 String 变成 int)。 -
sorted():排序。 -
distinct():去重。 -
limit(n)/skip(n):截取或跳过。
-
第三步:终端操作 (Terminal Operations)
-
特点: 返回值不是 Stream(是具体的结果或 void),会触发整个流水线的执行。
-
常用方法:
-
forEach(Consumer):遍历消费。 -
collect(Collectors.toList()):收集结果(转回 List, Set, Map)。 -
count():统计个数。 -
findFirst()/anyMatch():查找或匹配。
-
3. 通俗例子:筛选简历
假设你有一个 List<Resume>(简历列表),你要做以下操作:
-
过滤出“本科以上”学历的;
-
转换,只保留“姓名”字段;
-
限制只看前 5 份;
-
收集成一个 List。
List<String> result = resumeList.stream() // 1. 上流水线
.filter(r -> r.getDegree().equals("Bachelor")) // 2. 中间操作:过滤
.map(Resume::getName) // 3. 中间操作:转换 (只要名字)
.limit(5) // 4. 中间操作:截取
.collect(Collectors.toList()); // 5. 终端操作:打包带走4.Stream流的并行API是什么?
-
定义:
list.parallelStream() -
原理: 利用多核 CPU,把大数据拆分成多个小块(Fork/Join 框架),同时处理,最后合并结果。

-
适用场景: 数据量非常大(几万条以上)且任务计算密集时。
-
坑:
-
线程安全问题: 如果你在并行流里修改共享变量(比如往一个全局 List 里 add 数据),会出 Bug。
-
性能开销: 数据量小的时候,拆分线程的开销比直接算还要慢,别盲目用。
-
5.completableFuture怎么用的?
在 Java 8 之前,我们主要靠 Future 来做异步,但它很难用(只能干等结果 get(),这会阻塞当前线程。而且很难解决“任务A做完自动做任务B”这种场景。)。Java 8 引入的 CompletableFuture 是对 Future 的重大升级,实现了异步任务的编排组合(Chain & Combine)。
CompletableFuture 的优势:
-
非阻塞: 可以通过回调函数(Callback)在任务完成时自动触发后续操作,不需要死等。
-
任务编排: 轻松实现“串行执行”、“并行执行”、“AND 汇聚”、“OR 汇聚”等复杂逻辑。
| 类别 | 核心 API | 作用 |
|---|---|---|
| 创建 | supplyAsync | 开启异步任务(有返回值) |
| 转换 | thenApply | 拿到结果,转换处理,返回新结果 |
| 消费 | thenAccept | 拿到结果,消费掉(无返回) |
| 组合 | thenCombine | 任务 A + B 都完成,合并结果 |
| 竞争 | applyToEither | 任务 A 或 B 谁快用谁 |
| 全量 | allOf | 等待 N 个任务全部做完 |
| 异常 | exceptionally | 出了异常怎么办(兜底) |
Java 21 是一个非常重要的 LTS 版本,最核心的特性肯定是 虚拟线程 (Virtual Threads)。它让 Java 实现了轻量级的用户态线程,大大降低了高并发编程的门槛和资源消耗,这在以前可能需要用复杂的响应式编程才能做到,现在用同步的代码风格就能实现高吞吐量。
其次是 集合框架的升级 (Sequenced Collections),它统一了 List、Set、Map 中关于“有序操作”的 API,比如现在可以直接用 getFirst() 和 getLast(),不用再记各种不同的方法了。
还有就是语言语法的改进,比如 Switch 的模式匹配,现在的 Switch 可以直接匹配对象类型,还能处理 null 值,代码写起来比以前那种一长串的 if-else 优雅很多。