Java 8 的主要新特性,深入每个特性的背景、用法、实际应用场景,并提供代码示例,力求全面且易懂。Java 8(2014年3月发布)是 Java 语言的一次重大升级,引入了函数式编程、现代化 API 和性能优化,极大地提升了开发效率和代码可读性。以下是详细内容:

1. Lambda 表达式

背景
Lambda 表达式是 Java 8 引入的函数式编程核心特性,旨在减少样板代码,允许将行为作为参数传递。之前,Java 处理匿名函数(如匿名内部类)时代码冗长,Lambda 简化了这一过程。

特性:

  • 语法:(参数) -> 表达式(参数) -> { 语句块; }
  • 无需显式声明参数类型(编译器可推断)。
  • 可访问外部 final 或“有效 final”变量(即未被修改的变量)。
  • 用于实现函数式接口(只有一个抽象方法的接口,如 Runnable, Comparator)。

代码示例:

// 传统匿名内部类
Runnable r1 = new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello, World!");
    }
};

// Lambda 表达式
Runnable r2 = () -> System.out.println("Hello, World!");

// 带参数的 Lambda
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.forEach(name -> System.out.println("Hello, " + name));

应用场景

  • 简化集合操作(如 forEach)。
  • 实现事件监听器或回调函数。
  • 与 Stream API 结合进行数据处理。

注意事项

  • Lambda 表达式不能抛出比函数式接口方法更广的受检异常。
  • 性能上与匿名内部类相当,但代码更简洁。

2. Stream API

背景
Stream API(java.util.stream 包)是为了更高效、声明式地处理集合数据而设计的,支持函数式操作(如过滤、映射、归约)。它借鉴了 Scala 和其他语言的流处理概念,并支持并行处理以利用多核 CPU。

特性

  • 流(Stream):表示元素序列,支持惰性求值(仅在终端操作时计算)。
  • 操作类型
    • 中间操作(如 filter, map, sorted):返回新 Stream,惰性执行。
    • 终端操作(如 collect, forEach, reduce):触发计算,返回结果或副作用。
  • 并行流:通过 parallelStream()parallel() 启用多线程处理。
  • 常用方法:
    • filter:筛选元素。
    • map:转换元素。
    • flatMap:展平嵌套结构。
    • reduce:归约操作(如求和)。
    • collect:将流转换为集合或其他结构。

代码示例

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);

// 过滤偶数,映射为平方,收集到新列表
List<Integer> squares = numbers.stream()
    .filter(n -> n % 2 == 0)
    .map(n -> n * n)
    .collect(Collectors.toList());
System.out.println(squares); // 输出: [4, 16, 36]

// 并行流求和
int sum = numbers.parallelStream()
    .reduce(0, Integer::sum);
System.out.println(sum); // 输出: 15

应用场景

  • 数据过滤、转换和聚合(如统计、排序)。
  • 处理大数据集,尤其是并行流可提升性能。
  • 替代传统循环,代码更简洁。

注意事项

  • 流只能使用一次,重复使用会抛 IllegalStateException
  • 并行流需注意线程安全和性能开销(如小数据集可能更慢)。
  • 避免在流中产生副作用(如修改外部变量)。

3. Optional 类

背景
NullPointerException(NPE)是 Java 中常见问题,传统上通过显式 null 检查来规避。Optional 类(java.util.Optional)提供了一种更优雅的方式来表示可能为空的值,鼓励开发者显式处理空值。

特性

  • Optional 是一个容器,可能包含值或为空。
  • 常用方法:
    • of(T value):创建包含非空值的 Optional。
    • ofNullable(T value):创建可能为空的 Optional。
    • orElse(T other):值为空时返回默认值。
    • orElseThrow():值为空时抛异常。
    • ifPresent(Consumer):值存在时执行操作。
    • map(Function):对值进行转换。

代码示例

// 传统方式
String name = null;
if (name != null) {
    System.out.println(name.toUpperCase());
} else {
    System.out.println("Unknown");
}

// 使用 Optional
Optional<String> optionalName = Optional.ofNullable(name);
optionalName.map(String::toUpperCase)
    .ifPresent(System.out::println);
System.out.println(optionalName.orElse("Unknown")); // 输出: Unknown

应用场景

  • 方法返回值可能为空时,使用 Optional 代替返回 null
  • 链式调用(如 map)处理嵌套对象,避免多层 null 检查。
  • API 设计中明确表示返回值可能为空。

注意事项

  • 不要滥用 Optional(如用作方法参数,通常不推荐)。
  • Optional 本身不是 null,但仍需正确处理空值逻辑。

4. 默认方法(Default Methods)

背景
Java 接口传统上只能定义抽象方法,扩展接口时需修改所有实现类。默认方法允许在接口中提供默认实现,增强接口的扩展性,同时保持向后兼容性。

特性

  • 使用 default 关键字在接口中定义方法实现。
  • 实现类可选择覆盖默认方法。
  • 若实现类继承多个接口,且默认方法冲突,需显式重写。

代码示例

interface Vehicle {
    void start(); // 抽象方法
    default void stop() { // 默认方法
        System.out.println("Vehicle stopped");
    }
}

class Car implements Vehicle {
    @Override
    public void start() {
        System.out.println("Car started");
    }
}

public class Main {
    public static void main(String[] args) {
        Car car = new Car();
        car.start(); // 输出: Car started
        car.stop();  // 输出: Vehicle stopped
    }
}

应用场景

  • 扩展现有接口(如 Java 8 中的 Collection 添加 forEach)。
  • 提供可选功能,减少实现类的样板代码。
  • 支持接口的演进,保持 API 兼容性。

注意事项

  • 默认方法冲突需通过显式重写解决(如 ClassName.super.method() 调用特定接口的默认方法)。
  • 默认方法不能覆盖 Object 类的方法(如 toString)。

5. 方法引用(Method References)

背景
方法引用是 Lambda 表达式的简写形式,用于引用已有方法,进一步简化代码。它提高了代码可读性,尤其在 Stream API 中。

特性

  • 四种形式:
    • 静态方法引用:ClassName::staticMethod
    • 实例方法引用:instance::instanceMethodClassName::instanceMethod
    • 构造方法引用:ClassName::new
  • 编译器自动推断方法签名,需与函数式接口兼容。

代码示例

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

// Lambda 表达式
names.forEach(name -> System.out.println(name));

// 方法引用
names.forEach(System.out::println);

// 构造方法引用
List<String> upperNames = names.stream()
    .map(String::new) // 等价于 x -> new String(x)
    .collect(Collectors.toList());

应用场景

  • 替代简单的 Lambda 表达式,提高代码可读性。
  • 与 Stream API 结合,简化映射或归约操作。
  • 创建对象实例(如工厂方法)。

注意事项

  • 方法引用的签名必须与函数式接口的方法匹配。
  • 不能传递额外参数,需用 Lambda 表达式处理复杂逻辑。

6. 新的日期和时间 API(java.time 包)

背景
旧的 java.util.DateCalendar 类存在设计缺陷(如线程不安全、API 复杂)。Java 8 引入了基于 Joda-Time 的新 API(java.time 包),提供更直观、线程安全的日期时间处理。

特性

  • 核心类
    • LocalDate:仅日期(如 2025-08-01)。
    • LocalTime:仅时间(如 15:02:00)。
    • LocalDateTime:日期和时间(如 2025-08-01T15:02:00)。
    • ZonedDateTime:带时区的日期时间。
    • Instant:时间戳(机器时间)。
  • 不可变性:所有类都是不可变的,操作返回新实例。
  • 格式化:通过 DateTimeFormatter 自定义格式。
  • 时间计算:支持 plus, minus, Period, Duration 等。

代码示例

// 获取当前日期
LocalDate today = LocalDate.now(); // 2025-08-01
LocalDate tomorrow = today.plusDays(1); // 2025-08-02

// 格式化
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
String formatted = today.format(formatter); // 2025/08/01

// 计算时间间隔
LocalDate birthday = LocalDate.of(1990, 1, 1);
Period age = Period.between(birthday, today);
System.out.println("Age: " + age.getYears()); // 输出: Age: 35

// 处理时区
ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("America/New_York"));

应用场景

  • 日期时间计算(如计算年龄、到期日)。
  • 国际化应用中处理时区。
  • 日志系统记录精确时间戳。

注意事项

  • 新 API 线程安全,适合并发环境。
  • 与旧 API 互操作需使用 toInstant()from() 方法。

7. Nashorn JavaScript 引擎

背景
Java 8 引入 Nashorn 引擎(javax.script 包),替代老旧的 Rhino 引擎,允许在 JVM 上运行 JavaScript 代码,性能更高且支持 ECMAScript 5.1。

特性

  • 通过 ScriptEngine 执行 JavaScript。
  • 支持 Java 与 JavaScript 互操作(如调用 Java 方法)。
  • 提供 jjs 命令行工具运行 JavaScript 文件。

代码示例

ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
engine.eval("print('Hello from JavaScript!');");

// Java 调用 JavaScript
engine.eval("function add(a, b) { return a + b; }");
Invocable invocable = (Invocable) engine;
Object result = invocable.invokeFunction("add", 2, 3);
System.out.println(result); // 输出: 5

应用场景

  • 嵌入式脚本执行(如动态配置)。
  • 服务端 JavaScript 开发(早期 Node.js 替代方案)。
  • 调试或快速原型开发。

注意事项

  • Nashorn 在 Java 11 中标记为废弃,Java 15 中移除,推荐使用 GraalVM。
  • 性能不如现代 JavaScript 引擎(如 V8)。

8. 并行数组操作

背景
Java 8 增强了 java.util.Arrays 类,添加并行操作方法,利用多核 CPU 提高数组处理性能。

特性

  • parallelSort():并行排序数组。
  • parallelSetAll():并行设置数组元素。
  • parallelPrefix():并行计算前缀操作。

代码示例

int[] array = {5, 2, 8, 1, 9};
Arrays.parallelSort(array);
System.out.println(Arrays.toString(array)); // 输出: [1, 2, 5, 8, 9]

Arrays.parallelSetAll(array, i -> i * 2);
System.out.println(Arrays.toString(array)); // 输出: [0, 2, 4, 6, 8]

应用场景

  • 大规模数组排序或初始化。
  • 高性能计算任务。

注意事项

  • 小数组使用并行操作可能因线程开销更慢。
  • 确保操作无副作用(如线程安全)。

9. 其他重要改进

9.1 类型注解

  • Java 8 扩展了注解的使用范围,支持在任何类型使用(如方法参数、局部变量)。
  • 提供 @Repeatable 注解,允许重复应用同一注解。
  • 示例:
    @TypeAnnotation
    List<@TypeAnnotation String> list;
  • 应用:结合工具(如 Checker Framework)进行静态类型检查。

9.2 并发改进

  • CompletableFuture:支持异步编程,类似 Promise,提供链式调用和异常处理。
    • 示例:
      CompletableFuture.supplyAsync(() -> "Hello")
          .thenApply(s -> s + " World")
          .thenAccept(System.out::println); // 输出: Hello World
  • ConcurrentHashMap 改进:支持流式操作和新方法(如 forEach)。
  • StampedLock:提供比 ReentrantReadWriteLock 更灵活的读写锁。

9.3 JVM 改进

  • 移除永久代(PermGen):替换为元空间(Metaspace),动态分配内存,减少 OutOfMemoryError
  • 改进 JIT 编译器:优化 Lambda 表达式和 Stream 的性能。
  • G1 垃圾收集器增强:成为默认 GC,优化大堆性能。

总结

Java 8 的新特性极大地改变了 Java 编程范式:

  • Lambda 和 Stream 引入函数式编程,简化数据处理。
  • Optional 提高空值处理安全性。
  • 默认方法 增强接口灵活性。
  • 新日期时间 API 提供现代化时间处理。
  • 并发和 JVM 优化 提升性能。