Java 8 核心新特性
Java 8 核心新特性
Java 8 (发布于 2014 年) 是 Java 发展史上一个里程碑式的版本,它引入了大量革命性的新特性,极大地改变了 Java 的编程范式,使得代码更加简洁、高效,并更好地支持了函数式编程。
1. Lambda 表达式 (Lambda Expressions)
- 是什么:Lambda 表达式是 Java 中匿名函数的一种简洁表示形式。它可以看作是匿名内部类的一种简化,允许我们将函数作为方法的参数进行传递。
- 语法:
(参数列表) -> { 方法体 } - 解决了什么问题/带来了什么便利:
- 代码简洁:极大地减少了匿名内部类的冗余代码,使代码更紧凑、易读。
- 函数式编程:引入了函数式编程的风格,让开发者可以更专注于“做什么”,而不是“怎么做”。
- 示例:
import java.util.concurrent.Callable; public class LambdaDemo { public static void main(String[] args) throws Exception { // 传统方式:实现 Runnable 接口 new Thread(new Runnable() { @Override public void run() { System.out.println("传统方式: Hello from traditional Runnable!"); } }).start(); // Lambda 表达式方式:实现 Runnable 接口 new Thread(() -> System.out.println("Lambda 方式: Hello from Runnable Lambda!")).start(); // 传统方式:实现 Callable 接口 Callable<String> callableTraditional = new Callable<String>() { @Override public String call() throws Exception { return "传统方式: Result from Callable!"; } }; System.out.println(callableTraditional.call()); // Lambda 表达式方式:实现 Callable 接口 Callable<String> callableLambda = () -> "Lambda 方式: Result from Callable Lambda!"; System.out.println(callableLambda.call()); } }
1.1 Lambda 表达式的底层原理
这是一个比较深入的考察点。很多人误以为 Lambda 表达式只是匿名内部类的“语法糖”,但其底层实现完全不同。
-
核心机制:
invokedynamic指令- Lambda 表达式在编译后,并不会直接生成一个匿名内部类(
.class文件)。 - 相反,编译器会将 Lambda 表达式的代码逻辑,编译成一个私有的静态方法。
- 然后,在调用 Lambda 表达式的地方,编译器会生成一条
invokedynamic字节码指令。这条指令是 Java 7 引入的,用于实现动态语言支持。
- Lambda 表达式在编译后,并不会直接生成一个匿名内部类(
-
编译和执行流程:
- 编译阶段:
- 编译器将 Lambda 表达式的代码体编译成一个私有静态方法。
- 在调用处生成一条
invokedynamic指令,该指令会指向一个特殊的引导方法 (Bootstrap Method),通常是LambdaMetafactory.metafactory()。
- 运行阶段:
- JVM 第一次执行到
invokedynamic指令时,会调用其引导方法。 - 引导方法会动态地生成一个实现该函数式接口的类,并将调用委托给那个私有静态方法。
- 这个动态生成的类只在内存中存在,不会生成
.class文件,并且 JVM 可以对其进行高度优化。
- JVM 第一次执行到
- 编译阶段:
-
与匿名内部类的区别:
- 字节码层面:匿名内部类会为每个实例都生成一个单独的
.class文件,而 Lambda 表达式只会生成一个私有静态方法,体积更小。 this关键字的指向:- 在匿名内部类中,
this指的是匿名内部类自身的实例。 - 在 Lambda 表达式中,
this指的是包含该 Lambda 表达式的外部类的实例。
- 在匿名内部类中,
- 性能:
invokedynamic允许 JVM 在运行时进行更灵活的优化,理论上性能会比匿名内部类更好。
- 字节码层面:匿名内部类会为每个实例都生成一个单独的
2. Stream API (流式 API)
- 是什么:Stream API 是用于处理集合(Collections)的强大工具。它提供了一种声明式的、类似 SQL 的方式来对数据进行过滤 (filter)、映射 (map)、排序 (sort)、聚合 (reduce) 等操作。
- 特点:
- 非存储数据:Stream 不存储数据,它只是数据源(如集合、数组、I/O Channel)的视图。
- 惰性执行:Stream 的操作是惰性的,只有当终端操作(如
forEach,collect)被调用时,中间操作才会被执行。 - 不可变:Stream 的操作不会修改原始数据源。
- 可并行:Stream 可以很容易地转换为并行流 (
parallelStream()),从而在多核 CPU 上自动进行并行处理,提高性能。
- 解决了什么问题/带来了什么便利:
- 提高开发效率:让集合操作变得更加简洁、直观,避免了大量的
for循环和if-else。 - 代码可读性:链式操作,逻辑清晰。
- 提高开发效率:让集合操作变得更加简洁、直观,避免了大量的
- 示例:
import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class StreamAPIDemo { public static void main(String[] args) { List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Anna"); // 过滤出名字以 'A' 开头,并转换为大写,然后收集到一个新的 List List<String> filteredNames = names.stream() .filter(name -> name.startsWith("A")) .map(String::toUpperCase) .sorted() // 排序 .collect(Collectors.toList()); System.out.println("过滤并转换后的名字: " + filteredNames); // 输出:[ALICE, ANNA] // 计算所有名字的总长度 int totalLength = names.stream() .mapToInt(String::length) .sum(); System.out.println("所有名字的总长度: " + totalLength); } }
3. 函数式接口 (Functional Interfaces)
- 是什么:只包含一个抽象方法的接口。它们是 Lambda 表达式的类型基础。Java 8 为此引入了
@FunctionalInterface注解,用于强制编译器检查接口是否符合函数式接口的定义。 - Java 内置的常用函数式接口:
Predicate<T>: 接收一个T类型参数,返回boolean。用于判断。Consumer<T>: 接收一个T类型参数,无返回值。用于消费数据。Function<T, R>: 接收一个T类型参数,返回R类型结果。用于转换。Supplier<T>: 无参数,返回一个T类型结果。用于提供数据。
- 解决了什么问题/带来了什么便利:
- 为 Lambda 提供类型支持:所有 Lambda 表达式都必须实现一个函数式接口。
- 标准化通用操作:通过内置的函数式接口,可以方便地表示各种通用操作。
- 示例:
import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; public class FunctionalInterfaceDemo { public static void main(String[] args) { // Predicate: 判断字符串是否为空 Predicate<String> isEmpty = String::isEmpty; System.out.println("Is 'hello' empty? " + isEmpty.test("hello")); // false // Function: 字符串转整数 Function<String, Integer> stringToInt = Integer::valueOf; System.out.println("String '123' to int: " + stringToInt.apply("123")); // 123 // Consumer: 打印字符串 Consumer<String> printer = System.out::println; printer.accept("Hello, Consumer!"); // Hello, Consumer! // Supplier: 提供一个随机数 Supplier<Double> randomSupplier = Math::random; System.out.println("Random number: " + randomSupplier.get()); } }
4. 接口的默认方法 (Default Methods in Interfaces)
- 是什么:允许在接口中定义带有方法体的方法。使用
default关键字修饰。 - 解决了什么问题/带来了什么便利:
- 接口的平滑演进:在不破坏现有实现类的情况下,为接口添加新的功能。
- 模拟多重继承:一定程度上模拟了多重继承(多重实现),可以减少抽象类的使用。
- 示例:
interface MyShape { void draw(); // 抽象方法 // 默认方法,提供一个默认实现 default void getInfo() { System.out.println("This is a shape."); } // 静态方法 static void staticMethod() { System.out.println("This is a static method in interface."); } } class Circle implements MyShape { @Override public void draw() { System.out.println("Drawing a circle."); } } public class DefaultMethodDemo { public static void main(String[] args) { MyShape circle = new Circle(); circle.draw(); // 调用实现类的方法 circle.getInfo(); // 调用接口的默认方法 MyShape.staticMethod(); // 调用接口的静态方法 } }
5. 方法引用 (Method References)
- 是什么:一种简化 Lambda 表达式的语法糖,当 Lambda 表达式只是简单地调用一个已有方法时,可以使用方法引用。
- 语法:
- 静态方法引用:
类名::静态方法名(例如System.out::println) - 实例方法引用:
实例对象::实例方法名(例如str::length) - 特定类型的方法引用:
类名::实例方法名(例如String::length,等同于s -> s.length()) - 构造器引用:
类名::new(例如ArrayList::new)
- 静态方法引用:
- 解决了什么问题/带来了什么便利:
- 代码更简洁:进一步简化 Lambda 表达式。
- 提高可读性:代码意图更明确。
- 示例:
import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; import java.util.function.Supplier; import java.util.Arrays; // 补充 import 语句 import java.util.stream.Collectors; // 补充 import 语句 public class MethodReferenceDemo { public static void main(String[] args) { List<String> messages = new ArrayList<>(Arrays.asList("hello", "world", "java")); // 1. 静态方法引用: System.out::println messages.forEach(System.out::println); // 2. 特定类型的方法引用: String::length List<Integer> lengths = messages.stream() .map(String::length) .collect(Collectors.toList()); System.out.println("Lengths: " + lengths); // [5, 5, 4] // 3. 实例方法引用: (这里创建一个 Consumer 实例) String prefix = "MSG: "; // Consumer<String> logger = prefix::concat; // 这里的 concat 会返回新的 String,不能直接用 Consumer // 正确使用 Consumer 的方式 Consumer<String> logger = msg -> System.out.println(prefix.concat(msg)); logger.accept("test message"); // 4. 构造器引用: ArrayList::new Supplier<List<String>> listSupplier = ArrayList::new; List<String> newList = listSupplier.get(); newList.add("item"); System.out.println("New list via constructor reference: " + newList); } }
6. 其他重要改进
Optional类:用于解决NullPointerException问题,避免了繁琐的null检查,使代码更健壮、可读。- 新的 Date and Time API (
java.time包):解决了旧Date和CalendarAPI 的线程不安全、设计混乱、API 难以使用等问题,提供了更强大、易用的日期时间处理工具(如LocalDate,LocalTime,LocalDateTime)。 - JVM 内部改进:
- 方法区由永久代 (PermGen) 改为元空间 (Metaspace):解决了
PermGen内存溢出问题,元空间使用本地内存,受限于系统可用内存,从而避免了 JVM 内存区域的固定大小限制。 HashMap的底层优化:在哈希冲突严重时,链表会转换为红黑树,提高了HashMap在极端情况下的性能,解决了 JDK 1.7 扩容时的死循环问题。
- 方法区由永久代 (PermGen) 改为元空间 (Metaspace):解决了

浙公网安备 33010602011771号