Java8:Lambda表达式

目录

1、出现原因

2、概念

3、使用

[3.1 基本语法](#3.1 基本语法)

[3.2 函数式接口:Lambda的类型](#3.2 函数式接口:Lambda的类型)

[3.3 方法引用](#3.3 方法引用)

4、优点

5、限制

[5.1 只能用于函数式接口](#5.1 只能用于函数式接口)

[5.2 变量捕获限制](#5.2 变量捕获限制)

[5.3 this关键字含义不同](#5.3 this关键字含义不同)

[5.4 调试困难](#5.4 调试困难)

[5.5 性能考虑](#5.5 性能考虑)

[5.6 类型推断限制](#5.6 类型推断限制)

6、应用场景

[6.1 集合操作](#6.1 集合操作)

[6.2 事件处理](#6.2 事件处理)

[6.3 线程处理](#6.3 线程处理)

[6.4 条件执行](#6.4 条件执行)


1、出现原因

背景与需求

  • 代码冗余问题:在Java 8之前,使用匿名内部类实现函数式接口时代码冗长

  • 函数式编程趋势:受到函数式语言(如Scala、Python)的影响

  • 并行处理需求:多核处理器时代需要更简洁的并发编程方式

  • API设计改进:让集合操作等API更加灵活和表达性强

示例对比

java 复制代码
// Java 8 之前 - 使用匿名内部类
Collections.sort(list, new Comparator<String>() {
    @Override
    public int compare(String s1, String s2) {
        return s1.compareTo(s2);
    }
});

// Java 8 之后 - 使用Lambda表达式
Collections.sort(list, (s1, s2) -> s1.compareTo(s2));

2、概念

核心定义 :Lambda 表达式是一个匿名函数

  • 匿名:它没有显式的方法名。

  • 函数:它像方法一样,有参数列表、一个函数体(执行代码)以及可能的返回值。

  • 可传递:Lambda 表达式可以作为参数传递给方法,或者赋值给一个变量。这是它最强大的地方。

主要功能:

  • 简化函数式接口的实现

  • 支持函数式编程风格

  • 提高代码的可读性和简洁性

  • 便于并行处理

  • 增强集合操作能力

3、使用

3.1 基本语法

java 复制代码
(parameters) -> expression
// 或
(parameters) -> { statements; }
  • 参数列表 (parameters):

    • 类似于方法的参数列表,放在圆括号中。

    • 可以声明参数的类型,但编译器通常可以从上下文中推断出类型,因此大多数时候可以省略。

    • 如果只有一个参数,且类型可被推断,圆括号 () 也可以省略。

    • 如果没有参数,必须使用空括号 ()

  • 箭头操作符 ->:

    • 它将参数列表与 Lambda 主体分隔开。
  • Lambda 主体 { ... }:

    • 包含了要执行的代码。

    • 如果主体只有一条语句,花括号 {}return 关键字(如果有返回值)都可以省略。

    • 如果主体包含多条语句,则必须使用花括号 {},并且如果需要返回值,必须显式使用 return 语句。

语法示例:

java 复制代码
// 1. 无参数,返回值类型为 void
() -> System.out.println("Hello World")

// 2. 一个参数,无括号 (Consumer),主体为一条语句
s -> System.out.println(s)

// 3. 一个参数,有括号 (Function)
(s) -> s.length()

// 4. 多个参数,类型推断 (Comparator)
(a, b) -> a.compareTo(b)

// 5. 指定参数类型 (Comparator)
(String a, String b) -> a.compareTo(b)

// 6. 多行代码,需要 return 和 {}
(int a, int b) -> {
    int sum = a + b;
    return sum;
}

3.2 函数式接口:Lambda的类型

Lambda 表达式不能独立存在,它必须被赋值给一个函数式接口类型的变量。可以把 Lambda 表达式看作是实现了一个函数式接口的、简洁的匿名内部类。

**工作原理:**当在代码中写下一个 Lambda 表达式时,编译器需要知道这个表达式的"目标类型"。这个目标类型必须是一个函数式接口。然后,Lambda 表达式就相当于实现了该接口唯一的抽象方法。

函数式接口的定义 :一个只有一个抽象方法 的接口。关键点:

  • 单一抽象方法(SAM):这是最重要的规则。它只能有一个抽象方法。

  • 默认方法和静态方法 :函数式接口可以有任意数量的 default 方法或 static 方法。这些有实现的方法不计入"抽象方法"的数量。

  • @FunctionalInterface 注解:这个注解不是必须的,但强烈推荐使用。它的作用有:

    • 编译器检查:告诉编译器"请检查这个接口是否符合函数式接口的定义"。如果不符合(比如没有抽象方法或多个抽象方法),编译器会报错。

    • 文档化:让阅读代码的人一目了然地知道这是一个函数式接口,目的是为了配合 Lambda 表达式使用。

示例:

java 复制代码
// 1. 传统的匿名内部类方式
Runnable runnable1 = new Runnable() {
    @Override
    public void run() { // 实现唯一的抽象方法 'run'
        System.out.println("Running...");
    }
};

// 2. Lambda 表达式方式
Runnable runnable2 = () -> System.out.println("Running...");

在上面的例子中:

  • Runnable 是一个函数式接口(它只有一个 run() 方法)。

  • Lambda 表达式 () -> System.out.println("Running...") 就是 run() 方法的实现。

  • 变量 runnable2 的类型是 Runnable,这就是 Lambda 表达式的类型。

Java 8 在 java.util.function 包中为我们预定义了大量常用的函数式接口,覆盖了大部分常见场景:

接口 参数 返回值 描述 抽象方法 常见用途
Consumer<T> T void 消费型接口 void accept(T t) 遍历集合,打印 list.forEach(s -> System.out.println(s))
Supplier<T> T 供给型接口 T get() 工厂方法 () -> new ArrayList<>()
Function<T, R> T R 函数型接口 R apply(T t) 类型转换 s -> s.length()
Predicate<T> T boolean 断言型接口 boolean test(T t) 过滤集合 s -> s.startsWith("A")
UnaryOperator<T> T T 一元操作符 T apply(T t) s -> s.toUpperCase()
BinaryOperator<T> (T, T) T 二元操作符 T apply(T t1, T t2) (a, b) -> a + b
java 复制代码
// Function<T, R>:R apply(T t)
// 数据转换,如将字符串转为整数。
Function<String, Integer> stringToInteger = s -> Integer.parseInt(s);
Integer number = stringToInteger.apply("123"); // 返回 123

// Predicate<T>:boolean test(T t)
// 过滤集合中的元素
Predicate<String> isEmpty = s -> s == null || s.trim().isEmpty();
boolean result = isEmpty.test(" "); // 返回 true
List<String> list = Arrays.asList("A", "BB", "CCC");
List<String> filtered = list.stream().filter(s -> s.length() > 1).toList(); // [BB, CCC]

// Consumer<T>:void accept(T t)
// 遍历集合,打印元素
Consumer<String> printer = s -> System.out.println("Element: " + s);
printer.accept("Hello"); // 打印 "Element: Hello"
list.forEach(printer); // 遍历打印整个 list

// Supplier<T>:T get()
// 惰性求值,对象工厂
Supplier<Double> randomSupplier = () -> Math.random();
Double randomValue = randomSupplier.get(); // 获取一个随机数

// UnaryOperator<T>: 继承自 Function<T, T>,输入和输出类型相同。 T apply(T t)
UnaryOperator<String> toUpper = s -> s.toUpperCase();

// BinaryOperator<T>: 接受两个同类型的参数,返回一个同类型的结果。 T apply(T t1, T t2)
BinaryOperator<Integer> add = (a, b) -> a + b;

// BiFunction<T, U, R>: 接受两个参数 (T, U),返回结果 R。 R apply(T t, U u)

// BiConsumer<T, U>: 接受两个参数 (T, U),不返回结果。 void accept(T t, U u)

自定义函数式接口示例

java 复制代码
@FunctionalInterface
interface MyCalculator {
    int calculate(int a, int b); // 唯一的抽象方法
}

public class Test {
    public static void main(String[] args) {
        // 使用 Lambda 表达式实现这个接口
        MyCalculator adder = (x, y) -> x + y;
        MyCalculator multiplier = (x, y) -> x * y;

        System.out.println(adder.calculate(5, 3)); // 输出 8
        System.out.println(multiplier.calculate(5, 3)); // 输出 15
    }
}

3.3 方法引用

方法引用是 Lambda 表达式的一种更简洁的写法,当 Lambda 体仅仅是调用一个已存在的方法时使用。

语法目标对象或类 :: 方法名

java 复制代码
// 静态方法引用
List<Integer> numbers = Arrays.asList(1, 2, 3);
numbers.forEach(System.out::println);

// 实例方法引用
List<String> strings = Arrays.asList("a", "b", "c");
strings.forEach(String::toUpperCase);

// 构造方法引用
Supplier<List<String>> listSupplier = ArrayList::new;

4、优点

  • 代码简洁性
  • 函数式编程支持
  • 并行处理能力
  • 提高可读性
  • 延迟执行
java 复制代码
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// 函数式操作
int sum = numbers.stream()
                .filter(n -> n % 2 == 0)
                .map(n -> n * 2)
                .reduce(0, Integer::sum);

List<String> list = Arrays.asList("a", "b", "c");

// 串行处理
list.stream().forEach(System.out::println);
// 并行处理
list.parallelStream().forEach(System.out::println);

// 业务逻辑更清晰
List<Person> adults = people.stream()
    .filter(person -> person.getAge() >= 18)
    .sorted((p1, p2) -> p1.getName().compareTo(p2.getName()))
    .collect(Collectors.toList());

// 延迟执行
Supplier<String> lazyMessage = () -> {
    System.out.println("Computing message...");
    return "Hello World";
};
// 只有在get()调用时才会执行
String result = lazyMessage.get();

5、限制

5.1 只能用于函数式接口

java 复制代码
// 正确 - 函数式接口(只有一个抽象方法)
@FunctionalInterface
interface MyFunction {
    int operate(int a, int b);
}

MyFunction add = (a, b) -> a + b;

// 错误 - 多个抽象方法的接口不能使用Lambda
interface InvalidInterface {
    void method1();
    void method2();  // 编译错误
}

5.2 变量捕获限制

java 复制代码
void outerMethod() {
    int count = 0;  // 必须是final或等效final
    
    // 错误:在Lambda中修改外部变量
    // Runnable r = () -> count++;  // 编译错误
    
    // 正确:只能读取final或等效final变量
    Runnable r = () -> System.out.println(count);
}

访问局部变量 :Lambda 可以捕获外部方法的局部变量 ,但这些变量必须是 final 或等效 final (effectively final)。等效 final 意味着变量在初始化后值没有再被修改过。

  • 原因 :局部变量存在于栈上,而 Lambda 可能在另一个线程中执行,其生命周期可能比创建它的方法更长。为了保证数据一致性,Java 采用了值捕获,即复制变量的值。如果变量可变,就会导致数据不同步。

访问实例变量和静态变量:Lambda 可以自由地读取和修改外部类的实例变量和静态变量,因为它们存储在堆中,是共享的。

5.3 this关键字含义不同

this 关键字 :在 Lambda 表达式内部,this 指的是创建这个 Lambda 表达式的外部类实例 ,而不是 Lambda 表达式本身。这与匿名内部类不同(匿名内部类的 this 指向自身)。

java 复制代码
public class ThisDemo {
    private String value = "Outer class value";
    
    public void testLambda() {
        Runnable r = () -> {
            // 这里的this指向包含Lambda的外部类实例
            System.out.println("this.value: " + this.value);
            System.out.println("this.getClass(): " + this.getClass());
            
            // 不能在Lambda内部定义与外部变量同名的变量
            // String value = "Lambda value"; // 编译错误
        };
        r.run();
    }
    
    public static void main(String[] args) {
        new ThisDemo().testLambda();
    }
}

输出结果:

this.value: Outer class value

this.getClass(): class ThisDemo

5.4 调试困难

java 复制代码
// Lambda表达式在调试时可能显示为匿名方法
List<Integer> numbers = Arrays.asList(1, 2, 3);
numbers.stream()
       .map(n -> n * 2)          // 调试时可能显示为lambda$0
       .forEach(System.out::println);

5.5 性能考虑

java 复制代码
// 在性能关键场景需要谨慎使用
public void performanceTest() {
    // 频繁创建的Lambda可能影响性能
    for (int i = 0; i < 1000000; i++) {
        Runnable r = () -> System.out.println(i);
        // 每次循环都会创建新的Lambda实例
    }
}

5.6 类型推断限制

java 复制代码
// 在某些复杂情况下需要显式类型声明
// 编译错误:类型推断失败
// BinaryOperator add = (x, y) -> x + y;

// 正确:显式声明类型
BinaryOperator<Integer> add = (Integer x, Integer y) -> x + y;

6、应用场景

6.1 集合操作

java 复制代码
List<User> users = getUserList();

// 过滤、转换、收集
List<String> names = users.stream()
    .filter(user -> user.getAge() > 18)
    .map(User::getName)
    .sorted()
    .collect(Collectors.toList());

6.2 事件处理

java 复制代码
// Swing事件处理
JButton button = new JButton("Click");
button.addActionListener(e -> {
    System.out.println("Button clicked");
    performAction();
});

6.3 线程处理

java 复制代码
// 创建线程
new Thread(() -> {
    // 后台任务
    processData();
    updateUI();
}).start();

6.4 条件执行

java 复制代码
// 延迟执行和条件检查
public void processIfValid(Supplier<Boolean> validator, 
                          Runnable processor) {
    if (validator.get()) {
        processor.run();
    }
}

// 使用
processIfValid(() -> checkCondition(), () -> doWork());
相关推荐
小咕聊编程2 小时前
【含文档+PPT+源码】基于java web的篮球馆管理系统系统的设计与实现
java·开发语言
后端小张2 小时前
【JAVA 进阶】Mybatis-Plus 实战使用与最佳实践
java·spring boot·spring·spring cloud·tomcat·mybatis·mybatis plus
崎岖Qiu2 小时前
【设计模式笔记07】:迪米特法则
java·笔记·设计模式·迪米特法则
我狸才不是赔钱货3 小时前
Python的“环境之殇”:从Venv到Conda的终极抉择
开发语言·python·conda
努力进修3 小时前
Rust 语言入门基础教程:从环境搭建到 Cargo 工具链
开发语言·后端·rust
摇滚侠4 小时前
Spring Boot3零基础教程,SpringApplication 自定义 banner,笔记54
java·spring boot·笔记
青云交4 小时前
Java 大视界 -- Java 大数据机器学习模型在游戏用户行为分析与游戏平衡优化中的应用
java·大数据·机器学习·数据存储·模型构建·游戏用户行为分析·游戏平衡优化
暗武逢天7 小时前
Java导出写入固定Excel模板数据
java·导出数据·easyexcel·excel固定模板导出
摇滚侠7 小时前
Spring Boot3零基础教程,KafkaTemplate 发送消息,笔记77
java·spring boot·笔记·后端·kafka