理解Lambda表达式

Lambda 表达式是 Java 8 最重要的新特性,它让代码变得更简洁、更优雅。

一句话理解 Lambda

Lambda 就是一个匿名函数,可以理解为"一段可以传递的代码"。

好比点外卖:

  • 传统方式:打电话给餐厅,说一堆话(定义方法)
  • Lambda 方式:在APP上点"宫保鸡丁"(直接给实现)

从匿名类到 Lambda 的进化

传统写法(Java 8 之前)

// 1. 创建一个 Runnable 接口的实现
Runnable task = new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello World");
    }
};

Lambda 写法(Java 8+)

Runnable task = () -> System.out.println("Hello World");

对比:

  • 代码量:5行 → 1行
  • 可读性:啰嗦 → 简洁
  • 重点:模板代码 → 业务逻辑

Lambda 表达式语法

(参数列表) -> { 方法体 }

最简单的三种形式:

// 1. 无参数,无返回值
() -> System.out.println("Hello");

// 2. 一个参数,无返回值(可省略括号)
x -> System.out.println(x);
(x) -> System.out.println(x);

// 3. 多个参数,有返回值
(int a, int b) -> { return a + b; };
// 可简化为:
(a, b) -> a + b;  // 只有一条语句时,可省略 return 和 {}

核心:函数式接口

Lambda 必须配合函数式接口使用。

什么是函数式接口?

只有一个抽象方法的接口(可以有多个默认方法或静态方法)。

// 自定义函数式接口
@FunctionalInterface  // 注解,可选,但推荐加上
interface MyFunction {
    int operate(int a, int b);
    // 只能有一个抽象方法
    
    // 可以有默认方法
    default void print() {
        System.out.println("默认方法");
    }
    
    // 可以有静态方法
    static void hello() {
        System.out.println("静态方法");
    }
}

Java 内置的常用函数式接口

接口参数返回值用途示例
Consumer<T>Tvoid消费型接口(x) -> System.out.println(x)
Supplier<T>无T供给型接口() -> new Random().nextInt()
Function<T,R>TR函数型接口(x) -> x * 2
Predicate<T>Tboolean断言型接口(x) -> x > 0
Runnable无void无参无返回值() -> System.out.println("Hi")

实际应用场景

场景1:替代匿名内部类

// 传统方式
button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("按钮被点击");
    }
});

// Lambda 方式
button.addActionListener(e -> System.out.println("按钮被点击"));

场景2:遍历集合

List<String> list = Arrays.asList("Java", "Python", "C++");

// 传统 for 循环
for (String s : list) {
    System.out.println(s);
}

// 使用 forEach + Lambda
list.forEach(s -> System.out.println(s));

// 方法引用(更简洁)
list.forEach(System.out::println);

场景3:Stream API 结合(最常用!)

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

// 1. 过滤出偶数
List<Integer> evens = numbers.stream()
    .filter(n -> n % 2 == 0)  // Predicate: 判断是否为偶数
    .collect(Collectors.toList());  // [2, 4, 6, 8, 10]

// 2. 每个数乘以2
List<Integer> doubled = numbers.stream()
    .map(n -> n * 2)  // Function: 映射操作
    .collect(Collectors.toList());  // [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

// 3. 求和
int sum = numbers.stream()
    .reduce(0, (a, b) -> a + b);  // BinaryOperator: 累加
    // 或使用方法引用
    // .reduce(0, Integer::sum);

// 4. 排序
List<Integer> sorted = numbers.stream()
    .sorted((a, b) -> b - a)  // 降序排序
    .collect(Collectors.toList());

// 5. 分组
Map<String, List<Integer>> groups = numbers.stream()
    .collect(Collectors.groupingBy(
        n -> n > 5 ? "大于5" : "小于等于5"
    ));

场景4:线程创建

// 传统方式
Thread t1 = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("线程运行");
    }
});

// Lambda 方式
Thread t2 = new Thread(() -> {
    System.out.println("线程运行");
    // 可以写多行代码
    System.out.println("第二行");
});

t2.start();

场景5:Comparator 排序

List<String> names = Arrays.asList("张三", "李四", "王五");

// 传统方式
Collections.sort(names, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.compareTo(b);
    }
});

// Lambda 方式
Collections.sort(names, (a, b) -> a.compareTo(b));

// 更简洁的方式
names.sort((a, b) -> a.compareTo(b));
// 或使用方法引用
names.sort(String::compareTo);

方法引用

方法引用是 Lambda 的一种简洁写法,有4种形式:

// 1. 静态方法引用:类名::静态方法
Function<String, Integer> parser = Integer::parseInt;
// 等价于:s -> Integer.parseInt(s)

// 2. 实例方法引用:对象::实例方法
String str = "Hello";
Supplier<Integer> lengthSupplier = str::length;
// 等价于:() -> str.length()

// 3. 特定类型的任意对象方法引用:类名::实例方法
Function<String, String> upper = String::toUpperCase;
// 等价于:s -> s.toUpperCase()

// 4. 构造方法引用:类名::new
Supplier<List<String>> listSupplier = ArrayList::new;
// 等价于:() -> new ArrayList<>()

Function<Integer, String[]> arraySupplier = String[]::new;
// 等价于:size -> new String[size]

实际开发中的例子

例子1:数据处理

// 模拟从数据库查询的用户列表
List<User> users = Arrays.asList(
    new User("张三", 25, "技术部"),
    new User("李四", 30, "产品部"),
    new User("王五", 22, "技术部"),
    new User("赵六", 28, "市场部")
);

// 需求1:找出技术部的员工
List<User> techUsers = users.stream()
    .filter(user -> "技术部".equals(user.getDepartment()))
    .collect(Collectors.toList());

// 需求2:获取所有员工的姓名
List<String> names = users.stream()
    .map(User::getName)  // 方法引用
    .collect(Collectors.toList());

// 需求3:按部门分组
Map<String, List<User>> byDept = users.stream()
    .collect(Collectors.groupingBy(User::getDepartment));

// 需求4:计算平均年龄
double avgAge = users.stream()
    .mapToInt(User::getAge)  // 转为IntStream
    .average()
    .orElse(0.0);

// 需求5:找到年龄最大的员工
Optional<User> oldest = users.stream()
    .max(Comparator.comparingInt(User::getAge));
oldest.ifPresent(user -> 
    System.out.println("年龄最大的员工:" + user.getName())
);

例子2:文件操作

// 读取文件,过滤空行,转为大写
try {
    List<String> lines = Files.lines(Paths.get("data.txt"))
        .filter(line -> !line.trim().isEmpty())  // 过滤空行
        .map(String::toUpperCase)  // 转为大写
        .collect(Collectors.toList());
} catch (IOException e) {
    e.printStackTrace();
}

例子3:事件处理

// 在 Swing 或 JavaFX 中
JButton button = new JButton("点击我");

// 传统方式
button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        // 复杂的业务逻辑
        doSomething();
        updateUI();
        logAction();
    }
});

// Lambda 方式(多行代码用 {})
button.addActionListener(e -> {
    doSomething();
    updateUI();
    logAction();
});

// 如果方法体很复杂,可以提取方法
button.addActionListener(e -> handleButtonClick());

private void handleButtonClick() {
    doSomething();
    updateUI();
    logAction();
}

注意事项

1. 变量作用域

int num = 10;
Runnable r = () -> {
    // 可以读取外部变量(必须是 final 或 effectively final)
    System.out.println(num);  // ✅
    
    // 不能修改外部变量
    // num = 20;  // ❌ 编译错误
};

2. this 关键字的区别

public class LambdaThis {
    private String name = "外部类";
    
    public void test() {
        // 传统匿名类
        Runnable r1 = new Runnable() {
            private String name = "匿名类";
            @Override
            public void run() {
                System.out.println(this.name);  // "匿名类"
            }
        };
        
        // Lambda
        Runnable r2 = () -> {
            // System.out.println(this.name);  // "外部类"
            // Lambda 中的 this 指向外部类的实例
        };
    }
}

3. 性能考虑

// 简单操作适合用 Lambda
list.forEach(System.out::println);

// 复杂逻辑不适合
list.forEach(item -> {
    // 这里有很多行复杂逻辑...
    // 建议提取为独立方法
    processItem(item);
});

Lambda 与 Stream 的关系

Lambda 是基础,Stream 是应用:

// 没有 Lambda 的 Stream(难以想象)
list.stream()
    .filter(new Predicate<String>() {
        @Override
        public boolean test(String s) {
            return s.startsWith("A");
        }
    })
    .map(new Function<String, Integer>() {
        @Override
        public Integer apply(String s) {
            return s.length();
        }
    })
    .forEach(new Consumer<Integer>() {
        @Override
        public void accept(Integer n) {
            System.out.println(n);
        }
    });

// 有 Lambda 的 Stream(简洁优雅)
list.stream()
    .filter(s -> s.startsWith("A"))
    .map(String::length)
    .forEach(System.out::println);

学习路径建议

  1. 先掌握基本语法:(参数) -> 表达式
  2. 理解函数式接口:@FunctionalInterface
  3. 练习常用接口:Consumer、Function、Predicate、Supplier
  4. 结合 Stream API:这是 Lambda 最常用的场景
  5. 学习方法引用:让代码更简洁
  6. 理解原理:Lambda 的实现机制(invokedynamic)

练习任务

创建一个员工管理系统,使用 Lambda 实现:

// Employee 类
class Employee {
    private String name;
    private int age;
    private String department;
    private double salary;
    // getters/setters
}

// 任务:
List<Employee> employees = getEmployees();

// 1. 找出工资大于10000的员工
// 2. 按部门分组
// 3. 计算每个部门的平均工资
// 4. 找出年龄最大的3个员工
// 5. 将员工姓名拼接成字符串,用逗号分隔
// 6. 按工资降序排序

总结:Lambda 表达式让 Java 代码从"面向对象"向"函数式编程"迈进了一大步。它不是万能的,但在集合操作、事件处理、并行计算等场景下能显著提升代码的可读性和简洁性。

建议:多写、多练。刚开始可能不习惯,但用熟后会"回不去"了。

更多推荐