📑 Java 演进系列总览 | Java 8 | Java 11 | Java 17 | Java 21 | Java 25

Java 8 于 2014 年 3 月 18 日正式发布,是 Oracle 支持 roadmap 上首个长期支持(LTS)版本,也是 Java 历史上影响最深远的版本。它引入了 Lambda 表达式、Stream API 和新的日期时间 API,把函数式编程范式带入 Java 平台主语言;接口默认方法与 CompletableFuture 则分别解决了库演进与异步组合的痛点。至今大量企业代码库仍以 Java 8 为基线。

Lambda 与方法引用

Lambda 表达式为函数式接口(只有一个抽象方法的接口)提供了简洁的实例化语法。编译器依据目标类型(target typing)推断 Lambda 的参数类型与返回类型,同一个 Lambda 字面量在不同上下文中可以对应不同的函数式接口。方法引用(类名::方法名)是 Lambda 的进一步简化,当 Lambda 体只是调用一个已有方法时,直接引用方法名即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.function.Function;
import java.util.function.ToIntFunction;

public class TargetTyping {
public static void main(String[] args) {
// 同一个 Lambda 结构,由目标类型决定类型
Function<String, Integer> parser = s -> s.length();
ToIntFunction<String> counter = s -> s.length();

// 方法引用:Lambda 体仅为单一方法调用
Function<String, Integer> ref = String::length;

System.out.println(parser.apply("java")); // 4
System.out.println(counter.applyAsInt("lambda")); // 6
System.out.println(ref.apply("method")); // 6
}
}

目标类型推断发生在赋值、方法参数与返回值位置;Lambda 本身没有独立的类型。实践中优先用方法引用替代单行转发的 Lambda,this 的语义在两种形式下与外围实例一致。更系统的语法讲解见 Java Lambda

Stream API

Stream 把"做什么"与"怎么做"分离:中间操作(filtermapsorted)是惰性的,只记录处理管道,不触发计算;终端操作(collectforEachreduce)才驱动数据流过整个管道。这让声明式数据处理成为 Java 标准库的一等公民。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class LazyPipeline {
public static void main(String[] args) {
List<String> names = Arrays.asList(
"Alice", "Bob", "Charlie", "Ada");

// filter/map 是惰性描述,collect 才触发计算
String result = names.stream()
.filter(n -> {
System.out.println("filter: " + n);
return n.length() <= 3;
})
.map(String::toUpperCase)
.sorted()
.collect(Collectors.joining(", "));

System.out.println(result); // ADA, BOB
}
}

运行输出可见 filter 只对每个元素执行一次、且无中间集合被物化——惰性求值允许相邻中间操作在一次遍历中合并执行(如 filter + map 融合)。终端操作只能调用一次,消费后 Stream 即失效。深入用法参见 Java Stream

接口默认与静态方法

默认方法允许接口为已有抽象方法提供实现,静态方法允许接口自带工具函数。核心动机是二进制兼容的接口演进:库作者可以给接口追加方法而不破坏已编译的实现类——这正是 Collection 库能平滑接入 Stream 操作的前提。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
interface Meter {
double read();

// 默认方法:实现类可选择覆盖,也可直接继承
default boolean isHealthy() {
return read() > 0.0;
}

// 静态方法:与实现无关的工具入口
static Meter constant(double value) {
return () -> value;
}
}

public class Main {
public static void main(String[] args) {
Meter meter = Meter.constant(42.0);
System.out.println(meter.read()); // 42.0
System.out.println(meter.isHealthy()); // true
}
}

默认方法适合"为既有接口补能力"的场景,不应用于替代抽象类承担状态与构造器职责。多继承同名默认方法时,实现类必须显式覆盖并用 接口名.super.方法 消歧义。

Optional

Optional<T> 用类型系统显式表达"返回值可能不存在",取代返回 null 或哨兵值的旧约定。它的定位是 API 返回值的包装:调用方从签名即可知道必须处理缺失情形,编译器与工具也能静态发现漏检。

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
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

public class OptionalReturn {
private static final Map<String, String> CONFIG =
new HashMap<>();

static {
CONFIG.put("host", "localhost");
CONFIG.put("port", "8080");
}

// 返回类型即契约:可能无值
static Optional<String> get(String key) {
return Optional.ofNullable(CONFIG.get(key));
}

public static void main(String[] args) {
String host = get("host").orElse("127.0.0.1");
String env = get("user").orElseGet(() -> System.getenv("USER"));

get("port").ifPresent(p ->
System.out.println("port = " + p));

System.out.println(host + " / " + env);
}
}

注意 Optional 不是字段或集合元素的替代品:它设计给返回值,用作类字段会引入额外分配与序列化问题,Map 的值也不建议包 OptionalisPresent()/get() 组合应让位于 map/flatMap/orElse 等组合式 API。

java.time

java.time(JSR 310)取代了设计缺陷缠身的 Date/Calendar。核心类型全部不可变(immutable),方法返回新实例而非修改自身,天然线程安全;API 区分了人类时间(LocalDateLocalDateTimeZonedDateTime)与机器时间(Instant),并统一用 Duration/Period 表达时长。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.time.LocalDate;
import java.time.Period;
import java.time.ZoneId;
import java.time.ZonedDateTime;

public class JavaTime {
public static void main(String[] args) {
LocalDate release = LocalDate.of(2014, 3, 18);

// 不可变:plusMonths 返回新实例,release 不变
LocalDate gaPlus3 = release.plusMonths(3);
System.out.println(gaPlus3); // 2014-06-18

Period age = Period.between(release, LocalDate.now());
System.out.println(age.getYears()); // 12

ZonedDateTime tokyo = ZonedDateTime.now(
ZoneId.of("Asia/Tokyo"));
System.out.println(tokyo.getHour());
}
}

迁移时用 Date.toInstant()/Date.from(Instant) 在旧新 API 间转换,DateTimeFormatter 取代 SimpleDateFormat(后者非线程安全)。

CompletableFuture

CompletableFuture 实现了 FutureCompletionStage,把"提交任务并等待结果"升级为可组合的异步管道:thenApply/thenCompose/thenCombine 描述依赖关系,exceptionally 兜底异常。所有异步方法都提供显式指定 Executor 的重载,避免默认线程池带来的不可控并发。

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
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class AsyncPipeline {
public static void main(String[] args) throws Exception {
ExecutorService pool = Executors.newFixedThreadPool(2);

try {
String result =
CompletableFuture.supplyAsync(
() -> "java", pool)
.thenApply(String::toUpperCase)
.thenCombine(
CompletableFuture.supplyAsync(
() -> "8", pool),
(a, b) -> a + "-" + b)
.exceptionally(e -> "fallback")
.get(1, TimeUnit.SECONDS);

System.out.println(result); // JAVA-8
} finally {
pool.shutdown();
}
}
}

thenCompose 用于依赖另一个异步结果的链式调用(避免嵌套 CompletableFuture<CompletableFuture<T>>),allOf/anyOf 聚合多个阶段。生产代码中始终显式传入自己的线程池,默认的 ForkJoinPool.commonPool() 适合 CPU 密集型短任务,不适合阻塞 I/O。

常用特性总结

  • Lambda 与方法引用:函数式接口的简洁实例化,目标类型驱动推断
  • Stream API:惰性中间操作 + 终端操作的声明式数据处理管道
  • 接口默认/静态方法:二进制兼容的接口演进,Collection 流操作的基石
  • Optional:返回值缺失的显式类型化契约,替代 null 返回约定
  • java.time:不可变、线程安全的日期时间 API,区分人类与机器时间
  • CompletableFuture:可组合的异步编排,支持显式线程池选择
  • 其他:重复注解、类型注解、Base64 编解码、Metaspace 取代永久代

迁移与兼容性

从 Java 7 迁移到 Java 8 的二进制兼容性整体良好:现有字节码无需重新编译即可运行,接口默认方法也以二进制兼容为设计目标。需要关注的点:永久代移除带来 MaxPermSize 参数被忽略(改用 MaxMetaspaceSize);HashMap 在树化桶后迭代顺序语义变化;部分泛型推断变更(JDK 8 的目标类型推断前移到参数位置)可能暴露旧代码的编译歧义;安全基线升级到 TLS 1.2 相关默认值可能影响老服务端连接。向后方向上,Java 8 是后续所有 LTS 的语言基线,本文全部特性在 11/17/21/25 中保持可用。

参考资料