Skip to content

JDK 8 新增特性、使用方法与面试要点

JDK 8是企业Java开发最重要的分水岭。它不只是增加Lambda语法,还同时改变了集合处理、接口演进、日期时间、异步任务、并发统计、注解和JVM内存实现。

一、特性全景

类别新增或重要变化典型用途
语言Lambda、方法引用、类型注解、重复注解行为参数化、注解增强
接口默认方法、静态方法接口兼容演进
集合Stream、Spliterator、Map新方法过滤、聚合、并行拆分
空值Optional返回值表达缺失
时间java.time日期、时间点、时区和周期
异步CompletableFuture异步编排和并行聚合
并发LongAdder、StampedLock热点计数、读多写少
工具Base64、Arrays并行操作编解码、批量处理
JVMMetaspace替代PermGen类元数据改用本地内存
集合实现HashMap树化、ConcurrentHashMap重构极端冲突和并发性能

二、Lambda与函数式接口

Lambda把“行为”作为参数传递:

java
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

public class LambdaDemo {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Tom", "Amy", "Jerry");
        names.sort(Comparator.comparingInt(String::length)
                .thenComparing(String::compareTo));
        System.out.println(names);
    }
}

函数式接口只有一个抽象方法:

java
@FunctionalInterface
interface PriceRule {
    long calculate(long originalCent);
}

PriceRule discount = originalCent -> originalCent * 90 / 100;

常用标准接口:

接口输入输出场景
Predicate<T>Tboolean过滤、校验
Function<T,R>TR映射转换
Consumer<T>Tvoid消费、回调
Supplier<T>T延迟创建
UnaryOperator<T>TT同类型转换
BinaryOperator<T>T、TT归并

Lambda通常通过invokedynamicLambdaMetafactory链接,不应简单回答为“编译成一个匿名内部类”。被捕获的局部变量必须是final或effectively final,因为Lambda可能在方法返回后才执行,不能直接共享可变栈变量。

三、方法引用

形式示例等价Lambda
静态方法Integer::parseInts -> Integer.parseInt(s)
特定对象实例方法logger::infos -> logger.info(s)
任意对象实例方法String::lengths -> s.length()
构造器ArrayList::new() -> new ArrayList<>()

方法引用适合目标明确的简单转发;如果需要多步业务逻辑,普通Lambda或命名方法更易读。

四、Stream

java
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

List<Order> orders = loadOrders();

Map<String, Long> amountByCity = orders.stream()
        .filter(order -> order.getStatus() == OrderStatus.PAID)
        .collect(Collectors.groupingBy(
                Order::getCity,
                Collectors.summingLong(Order::getAmountCent)));

Stream执行链:

mermaid
flowchart LR
    A["数据源"] --> B["中间操作filter/map/sorted"]
    B --> C["终止操作collect/reduce/count"]
    C --> D["触发遍历并产生结果"]

重要规则:

  • Stream不是数据结构,不保存数据。
  • 中间操作通常是惰性的,没有终止操作就不执行。
  • 一个Stream只能消费一次。
  • 不要在mapfilter里修改外部共享状态。
  • JDK 8没有Stream.toList(),使用collect(Collectors.toList())
  • parallelStream()不保证更快,默认使用公共ForkJoinPool。
  • Stream中逐条调用数据库或远程接口会制造N+1问题。

常用Collector

java
Map<Long, Order> byId = orders.stream()
        .collect(Collectors.toMap(
                Order::getId,
                Function.identity(),
                (oldValue, newValue) -> newValue));

toMap遇到重复Key会抛异常,生产代码必须明确合并规则。groupingBy适合一对多分组,partitioningBy只分true/false两组。

五、Optional

java
Optional<User> user = repository.findById(userId);

String city = user
        .map(User::getAddress)
        .map(Address::getCity)
        .orElse("UNKNOWN");

正确边界:

推荐不推荐
方法返回值表达可能不存在Entity/DTO每个字段都包Optional
mapflatMap组合无判断直接get()
orElseGet延迟创建默认值把Optional变量本身设为null

orElse会提前计算参数,orElseGet只在缺值时调用Supplier:

java
User user = optional.orElseGet(this::loadDefaultUser);

六、接口默认方法和静态方法

java
interface PaymentService {
    PaymentResult pay(PaymentCommand command);

    default void validate(PaymentCommand command) {
        if (command == null || command.getAmountCent() <= 0) {
            throw new IllegalArgumentException("支付金额必须大于0");
        }
    }

    static String normalizeChannel(String channel) {
        return channel.trim().toUpperCase();
    }
}

默认方法让已经有大量实现类的接口增加方法而不立即破坏二进制兼容。冲突规则:类方法优先;更具体接口优先;两个无继承关系接口提供同签名默认方法时,实现类必须显式覆盖。

七、java.time日期时间API

类型表达什么示例
LocalDate无时区日期生日、账单日
LocalTime无时区时间每日营业时间
LocalDateTime无时区日期时间本地业务展示时间
InstantUTC时间线上的瞬间事件时间戳
ZonedDateTime带地区时区和夏令时规则跨地区会议
OffsetDateTime带固定UTC偏移API协议时间
Duration时间线上的秒/纳秒间隔接口耗时
Period年/月/日日期间隔账期、年龄
java
Instant createdAt = Instant.now();
ZonedDateTime shanghai = createdAt.atZone(ZoneId.of("Asia/Shanghai"));
String text = shanghai.format(DateTimeFormatter.ISO_ZONED_DATE_TIME);

不要用LocalDateTime表达全球唯一时间点,因为它不包含时区或偏移。DateTimeFormatter不可变且线程安全,可以安全复用;SimpleDateFormat则不是线程安全的。

八、CompletableFuture

java
ExecutorService executor = Executors.newFixedThreadPool(20);

CompletableFuture<User> userFuture = CompletableFuture.supplyAsync(
        () -> userClient.get(userId), executor);
CompletableFuture<List<Order>> orderFuture = CompletableFuture.supplyAsync(
        () -> orderClient.list(userId), executor);

CompletableFuture<UserView> result = userFuture.thenCombine(
        orderFuture, UserView::new);

UserView view = result
        .exceptionally(ex -> fallback(userId, ex))
        .join();
方法作用
thenApply同步转换上一步结果
thenCompose串联返回Future的异步操作,避免嵌套
thenCombine合并两个独立Future
allOf等待多个任务全部完成
anyOf任一个完成后继续
handle同时处理结果和异常

JDK 8没有orTimeoutcompleteOnTimeout,常需借助调度器、HTTP客户端超时或框架能力实现总时限。生产必须指定业务线程池,不要把阻塞IO无界地提交给公共ForkJoinPool。

九、Map和集合API增强

java
Map<String, Long> counter = new HashMap<>();
counter.merge("PAID", 1L, Long::sum);

User user = users.computeIfAbsent(userId, this::loadUser);

orders.removeIf(order -> order.isExpired());
orders.sort(Comparator.comparing(Order::getCreatedAt));
方法用途注意事项
getOrDefault缺失时给默认值不写入Map
putIfAbsent缺失时放入HashMap本身仍非线程安全
computeIfAbsent按Key延迟创建映射函数不要做慢调用或递归更新同Key
merge累计和合并合并函数返回null会删除映射
forEach遍历不适合复杂控制流
removeIf按条件删除修改原集合

十、并发增强

LongAdder

java
LongAdder success = new LongAdder();
success.increment();
long current = success.sum();

高竞争计数下,LongAdder通过分散热点降低对单个CAS变量的竞争;sum()不是与并发更新线性一致的原子快照,因此适合监控计数,不适合要求精确读改写语义的账户余额。

StampedLock

java
long stamp = lock.tryOptimisticRead();
double currentX = x;
double currentY = y;
if (!lock.validate(stamp)) {
    stamp = lock.readLock();
    try {
        currentX = x;
        currentY = y;
    } finally {
        lock.unlockRead(stamp);
    }
}

StampedLock支持乐观读,但不可重入,必须使用正确stamp解锁,使用复杂度高。普通业务优先选择不可变数据、ConcurrentHashMap或ReentrantReadWriteLock,确认读竞争瓶颈后再评估。

十一、Base64和其他工具API

java
String encoded = Base64.getUrlEncoder().withoutPadding()
        .encodeToString("order:1001".getBytes(StandardCharsets.UTF_8));
byte[] decoded = Base64.getUrlDecoder().decode(encoded);

Base64是编码而不是加密,不能用于保护密码或Token秘密。

Arrays.parallelSortArrays.parallelPrefix等并行数组操作适合足够大的纯内存计算;小数组或共享机器上不一定优于串行处理。

十二、注解增强

重复注解:

java
@Role("ADMIN")
@Role("AUDITOR")
public class AuditService {}

类型注解可以出现在类型使用位置,主要为静态分析框架提供信息。-parameters编译参数可以在Class文件中保留方法参数名,便于反射和框架参数绑定,但会改变元数据可见性和产物大小。

十三、集合底层变化

HashMap

JDK 8使用数组+链表+红黑树。桶中节点达到树化阈值且数组容量达到要求时才树化;容量过小时优先扩容。HashMap仍然不是线程安全容器,红黑树不能解决并发put的数据竞争。

ConcurrentHashMap

JDK 8移除JDK 7的Segment结构,采用Node数组、CAS插入空桶、冲突时synchronized锁桶头、链表/红黑树和多线程协助扩容。面试不能把Segment和桶级锁说成同一版本实现。

十四、JVM变化:PermGen到Metaspace

JDK 7常见HotSpotJDK 8常见HotSpot
类元数据主要在PermGen类元数据主要在本地内存Metaspace
常见PermGen space常见Metaspace
-XX:MaxPermSize-XX:MaxMetaspaceSize

移除永久代不表示不会发生类元数据OOM。动态代理、CGLIB、脚本、热部署和ClassLoader泄漏仍可能撑满Metaspace。

十五、Nashorn和JavaScript

JDK 8提供Nashorn JavaScript引擎,但它后来被废弃并从后续JDK移除。不要在新业务中把它当成长期稳定的平台能力;从JDK 8升级时应扫描javax.script和Nashorn专有API依赖。

十六、常见误区

误区正确理解
Lambda一定比匿名类快语义和实现不同,性能必须测量
Stream一定比for快Stream重在表达能力,不承诺更快
parallelStream适合所有大集合受任务粒度、公共池、阻塞和CPU影响
Optional解决所有NPE它只显式表达缺失,错误使用仍会NPE
CompletableFuture会自动创建线程实际由指定Executor或公共池执行
HashMap树化后线程安全数据结构优化与并发安全无关
Metaspace使用本地内存所以无限仍受机器、容器和配置限制

十七、面试回答

JDK 8主要新增了什么

JDK 8引入Lambda、方法引用、函数式接口和Stream,改变集合处理方式;引入Optional和新的java.time API;接口支持默认方法和静态方法;CompletableFuture支持异步任务组合;并发库增加LongAdder、StampedLock;HashMap增加红黑树,ConcurrentHashMap移除Segment并改为CAS加桶级锁;HotSpot移除PermGen并使用Metaspace承载主要类元数据。

Stream和集合有什么区别

集合负责保存数据,Stream描述一次数据处理流水线;中间操作通常惰性,终止操作才触发遍历。Stream只能消费一次,不应在操作中依赖共享可变状态,也不保证比for循环快。

CompletableFuture生产怎么用

明确业务线程池和队列,区分串行组合、并行聚合,设置下游与整体超时,处理异常和取消,避免在公共ForkJoinPool执行阻塞IO,并用指标监控active、queue、reject和任务耗时。

十八、关联学习

专题内容
LTS版本演进总览JDK 8、11、17、21对比和选型
JDK 7与8差异老项目写法和底层实现对比
Lambda与Stream函数式编程深入
日期时间APIjava.time类型和时区
CompletableFuture全过程异步执行链、线程池和异常
ConcurrentHashMap全过程CAS、桶锁和协助扩容

本章小结

JDK 8的价值不在于“代码可以少写几行”,而在于让行为参数化、声明式数据处理、类型安全时间、异步组合和并发统计进入标准库。生产使用必须同时理解惰性执行、线程池、共享状态、时间语义和集合线程安全边界。