Java Lambda
Java Lambda 表达式是 Java 8 引入的一种语法特性,用于简化函数式编程。它允许将函数作为方法参数传递,或者将代码作为数据来处理。Lambda 表达式本质上是一个匿名函数,可以替代匿名内部类,使代码更加简洁和易读。它主要用于实现函数式接口(Functional Interface),并广泛应用于集合操作、并行计算和事件处理等场景。
出现的原因
- 代码不易维护【匿名内部类的书写也不利于阅读】
- 多核 CPU 的兴起,涉及锁的编程算法不但容易出错,而且耗费时间。人们虽然开发了java.util.concurrent包和很多第三方类库,试图将并发抽象化,帮助程序员写出在多核 CPU 上运行良好的程序。很可惜,到目前为止,我们的成果还远远不够。面对大型数据集合,Java还欠缺高效的并行操作。开发者能够使用 Java 8编写复杂的集合处理算法,只需要简单修改一个方法,就能让代码在多核 CPU 上高效运行。
Lambda 表达式的主要特点:
- 匿名:没有显式的名称。
- 函数:它属于函数式编程的概念,可以接受参数并返回值。
- 简洁:相比匿名内部类,Lambda 表达式的语法更加简洁。
什么是函数式编程(Lambda)
ini
# 核心是:在思考问题时,使用不可变值和函数,函数对一个值进行处理,映射成另一个值。
# 通俗一点的解释:
一个方法中,一般会存在一段可以执行的代码块。函数式编程相当于直接将这个代码块封装成了一个接口类型(匿名内部类)
Lambda 表达式是一个匿名方法,将行为像数据一样进行传递
Lambda 表达式的常见结构:BinaryOperator<Integer> add = (x, y) → x + y
Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的
2. Java Lambda 快速实践
简单事例
typescript
import java.util.Arrays;
import java.util.List;
public class LambdaExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// 使用 Lambda 表达式遍历列表
names.forEach(name -> System.out.println(name));
// 使用 Lambda 表达式排序
names.sort((a, b) -> a.compareTo(b));
System.out.println(names); // 输出: [Alice, Bob, Charlie]
}
}
Java Lambda 详解
Java 中的 Lambda 表达式 是通过函数式接口(Functional Interface) 实现的。Lambda 表达式本质上是一个匿名函数,而函数式接口是 Lambda 表达式的目标类型。Java 编译器会将 Lambda 表达式转换为函数式接口的实例,从而实现对 Lambda 表达式的支持。
Java 中的 Lambda 表达式是通过 invokedynamic 指令和 Lambda 元工厂(Lambda Metafactory) 实现的。具体步骤如下:
bash
# 编译时
1. Lambda 表达式的捕获:
编译器会将 Lambda 表达式转换为一个静态方法(称为 Lambda 体方法),该方法包含 Lambda 表达式的逻辑。
如果 Lambda 表达式捕获了外部变量,这些变量会被作为参数传递给 Lambda 体方法。
2.生成 invokedynamic 指令:
编译器会生成一个 invokedynamic 指令,该指令指向 LambdaMetafactory.metafactory 方法。
invokedynamic 是 Java 7 引入的指令,用于支持动态语言特性(如 Lambda 表达式)。
# 运行时
1. 调用 LambdaMetafactory.metafactory:
在运行时,JVM 会调用 LambdaMetafactory.metafactory 方法,生成一个函数式接口的实例。
LambdaMetafactory.metafactory 方法会动态创建一个类,该类实现了目标函数式接口,并将 Lambda 体方法绑定到接口的抽象方法上。
2. 返回函数式接口实例:
LambdaMetafactory.metafactory 方法返回一个函数式接口的实例,该实例可以直接调用。
函数式接口的定义
函数式接口是 只有一个抽象方法 的接口。Java 8 引入了 @FunctionalInterface
注解,用于标识函数式接口。
less
// 1、该注解只能标记在"有且仅有一个抽象方法"的接口上。
// 2、JDK8接口中的静态方法和默认方法,都不算是抽象方法。
// 3、接口默认继承java.lang.Object,所以如果接口显示声明覆盖了Object中方法,那么也不算抽象方法。
// 4、该注解不是必须的,如果一个接口符合"函数式接口"定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
自定义函数式接口
java
// 自定义函数式接口
@FunctionalInterface
interface Greeting {
void sayHello(String name);
}
Lambda 表达式的语法
r
# Lambda 表达式的语法
(parameters) -> expression
或
(parameters) -> { statements; }
# 语法说明
parameters: Lambda 表达式的参数列表。如果没有参数,可以使用空括号 ()。
->: 箭头符号,将参数和 Lambda 表达式的主体分开。
expression: 单个表达式,Lambda 表达式的返回值。
{ statements; }:代码块,可以包含多条语句。如果使用代码块,需要使用 return 语句显式返回值。
示例
java
// 自定义函数式接口
@FunctionalInterface
interface Greeting {
void sayHello(String name);
}
// Lambda 表达式与函数式接口的绑定
// Lambda 表达式需要与函数式接口绑定,编译器会根据 Lambda 表达式的签名(参数类型和返回类型)匹配目标函数式接口的抽象方法。
Greeting greeting = name -> System.out.println("Hello, " + name);
greeting.sayHello("Alice"); // 输出: Hello, Alice
// 在上面的例子中:
// name -> System.out.println("Hello, " + name) 是一个 Lambda 表达式。
// Greeting 是一个函数式接口,其抽象方法 sayHello 的签名与 Lambda 表达式匹配。
Java中重要的函数接口
在 Java 中,函数式接口(Functional Interface)是 Lambda 表达式和方法引用的基础。Java 8 引入了 java.util.function
包,其中定义了许多常用的函数式接口。以下是这些接口的详细说明,包括它们的定义、用途和示例。
swift
# Predicate<T> 谓词 测试输入是否满足条
Predicate<T> boolean test(T t)
作用:Predicate<T> 是一个谓词接口,用于对输入类型为 T 的对象进行条件判断。它通常用于过滤操作。
示例:
Predicate<String> isLong = s -> s.length() > 5;
System.out.println(isLong.test("Hello")); // 输出: false
System.out.println(isLong.test("Hello World")); // 输出: true
# Function<T, R> 函数转换,输入类型 T ,输出类型 R
Function<T, R> R apply(T t)
作用:Function<T, R> 是一个函数接口,用于将输入类型为 T 的对象转换为输出类型为 R 的对象。它通常用于映射操作。
示例:
Function<String, Integer> lengthFunction = s -> s.length();
System.out.println(lengthFunction.apply("Hello")); // 输出: 5
# Consumer<T> 消费者,输入类型 T
Consumer<T> void accept(T t)
作用:Consumer<T> 是一个消费者接口,用于对输入类型为 T 的对象执行操作。它通常用于遍历集合或执行副作用操作。
示例:
Consumer<String> printConsumer = s -> System.out.println(s);
printConsumer.accept("Hello"); // 输出: Hello
# Supplier<T> 工厂方法
Supplier<T> T get()
作用:Supplier<T> 是一个工厂接口,用于生成类型为 T 的对象。它通常用于延迟计算或提供默认值。
示例:
Supplier<String> stringSupplier = () -> "Hello";
System.out.println(stringSupplier.get()); // 输出: Hello
# UnaryOperator<T> 函数转换的特例 输入和输出类型一样
UnaryOperator<T> T apply(T t)
作用:UnaryOperator<T> 是 Function<T, T> 的特例,表示输入和输出类型相同的函数。它通常用于对单个对象进行操作。
示例:
UnaryOperator<String> toUpperCase = s -> s.toUpperCase();
System.out.println(toUpperCase.apply("hello")); // 输出: HELLO
# BiFunction<T, U, R> 函数转换,接受两个参数 T, U,输出 R
BiFunction<T,U) R apply(T t, U u)
作用:BiFunction<T, U, R> 是一个函数接口,用于将两个输入类型为 T 和 U 的对象转换为输出类型为 R 的对象。它通常用于需要两个参数的映射操作。
示例:
BiFunction<String, String, Integer> concatLength = (s1, s2) -> (s1 + s2).length();
System.out.println(concatLength.apply("Hello", "World")); // 输出: 10
# BiConsumer<T, U> 消费者,接受两个参数
BiConsumer<T, U> void accept(T t, U u)
作用:BiConsumer<T, U> 是一个消费者接口,用于对两个输入类型为 T 和 U 的对象执行操作。它通常用于需要两个参数的副作用操作。
示例:
BiConsumer<String, Integer> printPair = (s, i) -> System.out.println(s + ": " + i);
printPair.accept("Age", 25); // 输出: Age: 25
# BiPredicate<T, U> 谓词,接受两个参数
BiPredicate<T, U> boolean test(T t, U u)
作用:BiPredicate<T, U> 是一个谓词接口,用于对两个输入类型为 T 和 U 的对象进行条件判断。它通常用于需要两个参数的过滤操作。
示例:
BiPredicate<String, Integer> isLongerThan = (s, len) -> s.length() > len;
System.out.println(isLongerThan.test("Hello", 3)); // 输出: true
System.out.println(isLongerThan.test("Hi", 5)); // 输出: false
Stream 和 Lambda 的关系
在 Java 中,Stream API 和 Lambda 表达式 是紧密相关的两个特性,它们共同为 Java 提供了强大的函数式编程能力。Stream API 依赖于 Lambda 表达式来实现对数据的处理,而 Lambda 表达式则为 Stream API 提供了简洁、灵活的语法支持。
Stream 是 Java 8 引入的一个 API,用于对集合(如 List
、Set
、Map
等)进行高效的数据处理。Stream 提供了一种声明式的编程方式,允许开发者通过链式调用(如 filter
、map
、reduce
等操作)来处理数据。Stream 是用函数式编程方式在集合类上进行复杂操作的工具。
Stream 和 Lambda 的结合
- Stream 的操作依赖于 Lambda 表达式 :Stream API 的许多方法(如
filter
、map
、forEach
等)都接受函数式接口作为参数,而 Lambda 表达式是实现这些函数式接口的简洁方式。 - Lambda 表达式为 Stream 提供了灵活的操作逻辑:通过 Lambda 表达式,开发者可以轻松定义 Stream 操作的具体行为(如过滤条件、映射规则等)。
事例:
ini
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<String> filteredNames = names.stream()
.filter(name -> name.startsWith("A")) // Lambda 表达式
.collect(Collectors.toList());
System.out.println(filteredNames); // 输出: [Alice]
在 Java 的 Stream API 中,惰性求值(Lazy Evaluation) 和 及早求值(Eager Evaluation) 是两种不同的计算策略。它们决定了 Stream 操作何时执行以及如何执行。理解这两种求值方式对于高效使用 Stream API 非常重要。
arduino
# 惰性求值
1. 惰性求值是指 Stream 的 中间操作(Intermediate Operations) 不会立即执行,而是等到 终端操作(Terminal Operation) 被调用时才会触发计算。中间操作只是定义了一个计算流程,但不会实际执行。
2. 惰性求值的特点
延迟计算:只有在需要结果时才会执行计算。
优化性能:通过合并多个操作,减少不必要的计算。
支持无限流:惰性求值使得处理无限流(如 Stream.iterate)成为可能。
3. 示例
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// 中间操作:filter 和 map 是惰性的,不会立即执行
Stream<String> stream = names.stream()
.filter(name -> {
System.out.println("Filtering: " + name);
return name.length() > 3;
})
.map(name -> {
System.out.println("Mapping: " + name);
return name.toUpperCase();
});
// 终端操作:触发计算
List<String> result = stream.collect(Collectors.toList());
// 输出:
// Filtering: Alice
// Mapping: Alice
// Filtering: Bob
// Filtering: Charlie
// Mapping: Charlie
arduino
# 及早求值
1. 及早求值是指操作会立即执行,而不是延迟到需要结果时才执行。在 Java 中,终端操作 是及早求值的,因为它们会触发 Stream 的处理并返回结果。
2. 及早求值的特点
立即计算:操作会立即执行,而不是延迟。
触发计算流程:终端操作会触发整个 Stream 的计算流程。
返回结果:终端操作会返回一个具体的值或副作用(如打印结果)。
3. 示例
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// 终端操作:forEach 是及早求值的,会立即执行
names.stream()
.filter(name -> {
System.out.println("Filtering: " + name);
return name.length() > 3;
})
.forEach(name -> System.out.println("Result: " + name));
// 输出:
// Filtering: Alice
// Result: Alice
// Filtering: Bob
// Filtering: Charlie
// Result: Charlie
总结
Java Lambda 表达式的引入,极大地提升了代码的简洁性和可读性,促进了函数式编程在 Java 中的应用。它在集合数据处理、并行计算等场景中表现尤为突出。自 Java 8 发布以来,Lambda 表达式已成为 Java 开发的标准工具,广泛应用于各类项目中。
更多内容欢迎关注 [ 小巫编程室 ] 公众号,喜欢文章的话,也希望能给小编点个赞或者转发,你们的喜欢与支持是小编最大的鼓励,小巫编程室感谢您的关注与支持。好好学习,天天向上(good good study day day up)。