快速入门-Java Lambda

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,用于对集合(如 ListSetMap 等)进行高效的数据处理。Stream 提供了一种声明式的编程方式,允许开发者通过链式调用(如 filtermapreduce 等操作)来处理数据。Stream 是用函数式编程方式在集合类上进行复杂操作的工具。

Stream 和 Lambda 的结合

  • Stream 的操作依赖于 Lambda 表达式 :Stream API 的许多方法(如 filtermapforEach 等)都接受函数式接口作为参数,而 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)。

相关推荐
无名之逆5 分钟前
轻量级、高性能的 Rust HTTP 服务器库 —— Hyperlane
服务器·开发语言·前端·后端·http·rust
无名之逆29 分钟前
探索Hyperlane:用Rust打造轻量级、高性能的Web后端框架
服务器·开发语言·前端·后端·算法·rust
穆骊瑶30 分钟前
Java语言的WebSocket
开发语言·后端·golang
追逐时光者1 小时前
精选 5 款基于 .NET 开源、功能强大的编辑器
后端·.net
uhakadotcom1 小时前
阿里云 MaxCompute SQLML:轻松实现机器学习
后端·面试·github
Asthenia04121 小时前
[4-Consumer]消费者端实现心跳功能
后端
狂炫一碗大米饭2 小时前
🧠前端面试高频考题---promise,从五个方面搞定它🛠️
前端·javascript·面试
TayTay的学习笔记2 小时前
LinkedList底层结构和源码分析(JDK1.8)
java·笔记·学习
Asthenia04122 小时前
[3-Consumer]回答面试官关于 MQ 项目中 Topic+Tag 二级消息过滤的思路整理
后端
Asthenia04122 小时前
[2-Consumer]如何回答面试官关于 MQ 轮子项目中 Push 和 Pull 混合消费的实现思路
后端