Java 8 Lambda表达式详解

一、什么是Lambda表达式

Lambda表达式是Java 8引入的最重要的新特性之一,它允许我们将函数作为方法参数,或者将代码作为数据来处理。本质上,Lambda表达式是一个匿名函数,它没有名称,但有参数列表、函数体和返回类型。

二、Lambda表达式语法

2.1 基本语法

java 复制代码
(parameters) -> expression

或者

java 复制代码
(parameters) -> { statements; }

2.2 语法组成

  • parameters:参数列表,可以有0个或多个参数,参数类型可以显式声明,也可以根据上下文推断(类型推断)。
  • ->:箭头符号,将参数列表和表达式或语句块分开。
  • expression:表达式,如果只有一条语句,可以省略大括号,并且该表达式的值即为 Lambda 表达式的返回值。
  • { statements; }:语句块,如果有多条语句,必须用大括号括起来,并且如果需要返回值,必须使用 return 语句。

2.3 Lambda表达式示例

java 复制代码
// 1. 无参数
() -> System.out.println("Hello Lambda")

// 2. 一个参数,可省略括号
s -> System.out.println(s)

// 3. 多个参数
(a, b) -> a + b

// 4. 带类型声明的参数
(String s1, String s2) -> s1.compareTo(s2)

// 5. 带代码块的Lambda
(int a, int b) -> {
    int sum = a + b;
    return sum;
}

三、Lambda表达式的优缺点

优点:

  1. 代码简洁:Lambda表达式可以替代匿名内部类,减少模板代码,使代码更清晰。

    java 复制代码
    // 传统方式
    Collections.sort(list, new Comparator<String>() {
        @Override
        public int compare(String s1, String s2) {
            return s1.compareTo(s2);
        }
    });
    
    // Lambda方式
    Collections.sort(list, (s1, s2) -> s1.compareTo(s2));
  2. 函数式编程:引入函数式编程风格,使得处理集合和数据流更加方便,结合Stream API可以写出更声明式的代码。支持将函数作为参数传递,为设计更抽象的代码提供了可能。

    java 复制代码
    // 函数组合示例
    Function<String, String> toUpper = String::toUpperCase;
    Function<String, String> addExclamation = s -> s + "!";
    
    Function<String, String> pipeline = toUpper.andThen(addExclamation);
    System.out.println(pipeline.apply("hello")); // 输出: HELLO!
  3. 并行处理能力:与Stream API结合,可以更容易地编写并行代码,提高多核处理器上的性能。

    java 复制代码
    // 串行处理
    List<String> result = list.stream()
        .filter(s -> s.length() > 3)
        .collect(Collectors.toList());
    
    // 并行处理
    List<String> result = list.parallelStream()
        .filter(s -> s.length() > 3)
        .collect(Collectors.toList());
  4. 增强API:使Java的API更加强大和灵活,例如集合框架的批量操作。

    java 复制代码
    // 更灵活的API设计
    public void processData(List<String> data, Predicate<String> filter, 
                           Function<String, String> mapper) {
        data.stream()
            .filter(filter)
            .map(mapper)
            .forEach(System.out::println);
    }
    
    // 使用
    processData(data, s -> s.startsWith("A"), String::toUpperCase);

缺点:

  1. 调试困难:Lambda表达式的堆栈跟踪可能比传统方法复杂,给调试带来一定困难。

    java 复制代码
    List<String> result = data.stream()
        .filter(s -> s.length() > 5)           // 调试时难以设置断点
        .map(s -> s.toUpperCase())             // 在Lambda内部调试复杂
        .collect(Collectors.toList());
  2. 性能开销:虽然Lambda表达式在大多数情况下性能不错,但在某些情况下(如捕获变量的Lambda)可能会引入额外的性能开销,因为需要生成新的类。但是,JVM会进行优化,并且随着JVM的升级,这种开销在减少。

    java 复制代码
    // Lambda表达式会生成额外的类文件
    IntStream.range(0, 1000)
        .map(i -> i * 2)        // 每个Lambda都会生成一个类
        .filter(i -> i > 100)   // 增加类加载开销
        .sum();
  3. 可读性:对于不熟悉函数式编程的开发者来说,Lambda表达式可能降低代码的可读性。过度使用或嵌套使用Lambda表达式会使代码难以理解。

    java 复制代码
    // 复杂的Lambda链可能难以理解
    List<String> result = data.stream()
        .flatMap(s -> Arrays.stream(s.split(",")))
        .collect(Collectors.groupingBy(
            s -> s.substring(0, 1),
            Collectors.mapping(
                s -> s.toUpperCase(),
                Collectors.filtering(
                    s -> s.length() > 3,
                    Collectors.toList()
                )
            )
        ));
  4. 变量捕获限制:Lambda表达式只能用于函数式接口(只有一个抽象方法的接口)。此外,在Lambda表达式中不能修改外部变量(必须是final或effectively final)。

    java 复制代码
    public void problematicLambda() {
        int count = 0;
        List<String> data = Arrays.asList("a", "b", "c");
        
        data.forEach(s -> {
            // count++;  // 编译错误:被Lambda表达式引用的局部变量必须是final或等效final的
            System.out.println(s);
        });
    }
  5. 异常处理复杂:Lambda中的异常处理比较麻烦,需要包装受检异常。

    java 复制代码
    // Lambda中的异常处理比较麻烦
    List<String> files = Arrays.asList("file1.txt", "file2.txt");
    
    files.stream()
        .map(filename -> {
            try {
                return Files.readString(Path.of(filename));
            } catch (IOException e) {
                throw new RuntimeException(e);  // 需要包装受检异常
            }
        })
        .forEach(System.out::println);

四、函数式接口

Lambda表达式需要函数式接口的支持。

  • 只有一个抽象方法的接口。
  • 可以使用@FunctionalInterface注解标记。
java 复制代码
// 自定义函数式接口
@FunctionalInterface
interface MyFunction {
    int operate(int a, int b);
}

public class LambdaDemo {
    public static void main(String[] args) {
        // 使用Lambda表达式实现函数式接口
        MyFunction addition = (a, b) -> a + b;
        MyFunction multiplication = (a, b) -> a * b;
        
        System.out.println(addition.operate(5, 3)); // 输出: 8
        System.out.println(multiplication.operate(5, 3)); // 输出: 15
    }
}

内置的函数式接口:

java 复制代码
import java.util.function.*;

public class BuiltInFunctionalInterfaces {
    public static void main(String[] args) {
        
        // 1. Predicate<T> - 接受一个参数,返回boolean
        Predicate<String> isLong = s -> s.length() > 5;
        System.out.println(isLong.test("Hello")); // false
        
        // 2. Function<T,R> - 接受一个参数,返回一个结果
        Function<String, Integer> stringLength = s -> s.length();
        System.out.println(stringLength.apply("Java")); // 4
        
        // 3. Consumer<T> - 接受一个参数,无返回值
        Consumer<String> printer = s -> System.out.println(s);
        printer.accept("Hello Consumer");
        
        // 4. Supplier<T> - 无参数,返回一个结果
        Supplier<Double> randomSupplier = () -> Math.random();
        System.out.println(randomSupplier.get());
        
        // 5. UnaryOperator<T> - 接受一个参数,返回同类型结果
        UnaryOperator<String> toUpper = s -> s.toUpperCase();
        System.out.println(toUpper.apply("hello")); // HELLO
        
        // 6. BinaryOperator<T> - 接受两个同类型参数,返回同类型结果
        BinaryOperator<Integer> max = (a, b) -> a > b ? a : b;
        System.out.println(max.apply(10, 20)); // 20
    }
}

五、方法引用

方法引用是Lambda表达式的一种简写形式。方法引用的四种形式:

java 复制代码
import java.util.*;

public class MethodReferenceDemo {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
        
        // 1. 静态方法引用: ClassName::staticMethod
        names.forEach(MethodReferenceDemo::printStatic);
        
        // 2. 实例方法引用: instance::instanceMethod
        MethodReferenceDemo demo = new MethodReferenceDemo();
        names.forEach(demo::printInstance);
        
        // 3. 特定类型的任意对象的方法引用: ClassName::instanceMethod
        names.sort(String::compareToIgnoreCase);
        
        // 4. 构造方法引用: ClassName::new
        List<String> upperNames = names.stream()
            .map(String::new)
            .map(String::toUpperCase)
            .collect(Collectors.toList());
    }
    
    public static void printStatic(String s) {
        System.out.println("Static: " + s);
    }
    
    public void printInstance(String s) {
        System.out.println("Instance: " + s);
    }
}

六、变量捕获

Lambda表达式可以捕获外部变量,但有一些限制:

java 复制代码
public class VariableCapture {
    public static void main(String[] args) {
        String externalString = "Hello";
        final int finalNumber = 42;
        
        // Lambda可以捕获final或effectively final的变量
        Runnable r = () -> {
            System.out.println(externalString);
            System.out.println(finalNumber);
            // externalString = "Changed"; // 错误:外部变量必须是final或effectively final
        };
        
        new Thread(r).start();
    }
}

七、Stream API 常用方法

Stream API 提供了强大的数据处理能力。Stream 不存储数据,而是对源数据(如集合)进行各种计算操作。以下是常用的方法:

  • 中间操作:filter, map, flatMap, distinct, sorted, limit, skip, peek
  • 终端操作:forEach, collect, reduce, toArray, count, match, find
  • 数值流:mapToInt, sum, average, range
  • 并行处理:parallelStream

1. 中间操作

中间操作返回一个新的 Stream,可以链式调用。

1.1 filter() - 过滤

过滤元素,只保留满足条件的元素。

java 复制代码
List<String> list = Arrays.asList("apple", "banana", "cherry", "date");

// 过滤长度大于5的字符串
List<String> result = list.stream()
    .filter(s -> s.length() > 5)
    .collect(Collectors.toList());
// result: ["banana", "cherry"]

// 过滤包含特定字符的字符串
List<String> containsA = list.stream()
    .filter(s -> s.contains("a"))
    .collect(Collectors.toList());
// containsA: ["apple", "banana", "date"]

1.2 map() - 映射转换

将元素映射为另一种类型。

java 复制代码
List<String> list = Arrays.asList("apple", "banana", "cherry");

// 转换为大写
List<String> upperCase = list.stream()
    .map(String::toUpperCase)
    .collect(Collectors.toList());
// upperCase: ["APPLE", "BANANA", "CHERRY"]

// 获取字符串长度
List<Integer> lengths = list.stream()
    .map(String::length)
    .collect(Collectors.toList());
// lengths: [5, 6, 6]

// 对象属性映射
List<Person> people = Arrays.asList(
    new Person("Alice", 25),
    new Person("Bob", 30)
);
List<String> names = people.stream()
    .map(Person::getName)
    .collect(Collectors.toList());
// names: ["Alice", "Bob"]

1.3 flatMap() - 扁平化映射

将每个元素转换为一个 Stream,然后将所有 Stream 连接成一个 Stream。

java 复制代码
List<List<String>> listOfLists = Arrays.asList(
    Arrays.asList("a", "b"),
    Arrays.asList("c", "d"),
    Arrays.asList("e", "f")
);

// 将多个列表合并为一个流
List<String> flatList = listOfLists.stream()
    .flatMap(List::stream)
    .collect(Collectors.toList());
// flatList: ["a", "b", "c", "d", "e", "f"]

// 拆分字符串并扁平化
List<String> words = Arrays.asList("hello world", "java stream");
List<String> individualWords = words.stream()
    .flatMap(s -> Arrays.stream(s.split(" ")))
    .collect(Collectors.toList());
// individualWords: ["hello", "world", "java", "stream"]

1.4 distinct()

去重,根据元素的 equals 方法判断。

java 复制代码
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3, 4);

List<Integer> distinctNumbers = numbers.stream()
    .distinct()
    .collect(Collectors.toList());
// distinctNumbers: [1, 2, 3, 4]

// 对象去重(需要重写equals和hashCode)
List<Person> people = Arrays.asList(
    new Person("Alice", 25),
    new Person("Alice", 25),
    new Person("Bob", 30)
);
List<Person> distinctPeople = people.stream()
    .distinct()
    .collect(Collectors.toList());

1.5 sorted() - 排序

排序,无参方法按自然序排序,有参方法按比较器排序。

java 复制代码
List<String> list = Arrays.asList("banana", "apple", "cherry");

// 自然排序
List<String> naturalSorted = list.stream()
    .sorted()
    .collect(Collectors.toList());
// naturalSorted: ["apple", "banana", "cherry"]

// 自定义排序
List<String> lengthSorted = list.stream()
    .sorted((s1, s2) -> Integer.compare(s1.length(), s2.length()))
    .collect(Collectors.toList());
// lengthSorted: ["apple", "banana", "cherry"] (如果长度相同)

// 使用方法引用
List<String> reverseSorted = list.stream()
    .sorted(Comparator.reverseOrder())
    .collect(Collectors.toList());
// reverseSorted: ["cherry", "banana", "apple"]

1.6 limit() 和 skip() - 限制和跳过

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

// 取前5个元素
List<Integer> firstFive = numbers.stream()
    .limit(5)
    .collect(Collectors.toList());
// firstFive: [1, 2, 3, 4, 5]

// 跳过前3个元素,取接下来的5个
List<Integer> skipThreeTakeFive = numbers.stream()
    .skip(3)
    .limit(5)
    .collect(Collectors.toList());
// skipThreeTakeFive: [4, 5, 6, 7, 8]

// 分页实现
int pageSize = 3;
int pageNumber = 1; // 第二页(从0开始)
List<Integer> page = numbers.stream()
    .skip(pageNumber * pageSize)
    .limit(pageSize)
    .collect(Collectors.toList());
// page: [4, 5, 6]

1.7 peek() - 查看元素(调试用)

对每个元素执行一个操作,主要用于调试。

java 复制代码
List<String> list = Arrays.asList("apple", "banana", "cherry");

List<String> result = list.stream()
    .peek(s -> System.out.println("Before filter: " + s))
    .filter(s -> s.length() > 5)
    .peek(s -> System.out.println("After filter: " + s))
    .collect(Collectors.toList());
// 输出:
// Before filter: apple
// Before filter: banana
// After filter: banana
// Before filter: cherry
// After filter: cherry

2. 终端操作

终端操作会触发 Stream 的处理,并产生一个结果或副作用。

2.1 forEach() - 遍历

对每个元素执行一个操作。

java 复制代码
List<String> list = Arrays.asList("apple", "banana", "cherry");

// 遍历输出
list.stream().forEach(System.out::println);

// 执行操作
list.stream().forEach(s -> {
    String upper = s.toUpperCase();
    System.out.println(upper);
});

2.2 collect() - 收集结果

将 Stream 中的元素收集到容器中,常用的 Collectors 方法有 toList, toSet, toMap 等。

java 复制代码
List<String> list = Arrays.asList("apple", "banana", "cherry", "apple");

// 转换为List
List<String> resultList = list.stream()
    .filter(s -> s.length() > 5)
    .collect(Collectors.toList());

// 转换为Set(自动去重)
Set<String> resultSet = list.stream()
    .collect(Collectors.toSet());

// 转换为Map
Map<String, Integer> lengthMap = list.stream()
    .distinct()
    .collect(Collectors.toMap(
        s -> s,                    // key
        String::length             // value
    ));
// lengthMap: {apple=5, banana=6, cherry=6}

// 分组
Map<Integer, List<String>> groupByLength = list.stream()
    .collect(Collectors.groupingBy(String::length));
// groupByLength: {5=[apple], 6=[banana, cherry]}

// 分区
Map<Boolean, List<String>> partitionByLength = list.stream()
    .collect(Collectors.partitioningBy(s -> s.length() > 5));
// partitionByLength: {false=[apple], true=[banana, cherry]}

// 连接字符串
String joined = list.stream()
    .collect(Collectors.joining(", ", "[", "]"));
// joined: "[apple, banana, cherry, apple]"

2.3 reduce() - 归约操作

将 Stream 中的元素组合起来,得到一个结果。

java 复制代码
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// 求和
Optional<Integer> sum = numbers.stream()
    .reduce((a, b) -> a + b);
// sum: 15

// 求和(带初始值)
Integer sumWithIdentity = numbers.stream()
    .reduce(0, Integer::sum);
// sumWithIdentity: 15

// 求最大值
Optional<Integer> max = numbers.stream()
    .reduce(Integer::max);
// max: 5

// 字符串连接
List<String> words = Arrays.asList("Hello", "World", "!");
String sentence = words.stream()
    .reduce("", (a, b) -> a + " " + b)
    .trim();
// sentence: "Hello World !"

2.4 count() - 计数

计算元素个数。

java 复制代码
List<String> list = Arrays.asList("apple", "banana", "cherry");

long count = list.stream().count();
// count: 3

long countLongWords = list.stream()
    .filter(s -> s.length() > 5)
    .count();
// countLongWords: 2

2.5 anyMatch() / allMatch() / noneMatch() - 匹配检查

java 复制代码
List<String> list = Arrays.asList("apple", "banana", "cherry");

// 是否有任意元素匹配条件
boolean anyMatch = list.stream()
    .anyMatch(s -> s.startsWith("a"));
// anyMatch: true

// 是否所有元素都匹配条件
boolean allMatch = list.stream()
    .allMatch(s -> s.length() >= 5);
// allMatch: true

// 是否没有元素匹配条件
boolean noneMatch = list.stream()
    .noneMatch(s -> s.contains("z"));
// noneMatch: true

2.6 findFirst() / findAny() - 查找元素

java 复制代码
List<String> list = Arrays.asList("apple", "banana", "cherry");

// 查找第一个元素
Optional<String> first = list.stream()
    .filter(s -> s.startsWith("b"))
    .findFirst();
// first: "banana"

// 查找任意元素(在并行流中更高效)
Optional<String> any = list.stream()
    .parallel()
    .filter(s -> s.length() > 5)
    .findAny();
// any: 可能是 "banana" 或 "cherry"

3. 数值流特化方法

对于数值类型的流,有专门的优化方法。

java 复制代码
List<String> stringNumbers = Arrays.asList("1", "2", "3", "4", "5");

// 转换为IntStream
IntStream intStream = stringNumbers.stream()
    .mapToInt(Integer::parseInt);

// 求和
int sum = stringNumbers.stream()
    .mapToInt(Integer::parseInt)
    .sum();
// sum: 15

// 求平均值
double average = stringNumbers.stream()
    .mapToInt(Integer::parseInt)
    .average()
    .orElse(0.0);
// average: 3.0

// 最大值
OptionalInt max = stringNumbers.stream()
    .mapToInt(Integer::parseInt)
    .max();
// max: 5

// 范围生成
IntStream range = IntStream.range(1, 6); // 1,2,3,4,5
IntStream rangeClosed = IntStream.rangeClosed(1, 5); // 1,2,3,4,5

4. 并行流处理

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

// 顺序流
long sequentialTime = System.currentTimeMillis();
int sequentialSum = numbers.stream()
    .mapToInt(Integer::intValue)
    .sum();
sequentialTime = System.currentTimeMillis() - sequentialTime;

// 并行流
long parallelTime = System.currentTimeMillis();
int parallelSum = numbers.parallelStream()
    .mapToInt(Integer::intValue)
    .sum();
parallelTime = System.currentTimeMillis() - parallelTime;

System.out.println("Sequential time: " + sequentialTime + "ms");
System.out.println("Parallel time: " + parallelTime + "ms");

八、Lambda表达式在实际中的应用

8.1 集合操作

java 复制代码
import java.util.*;
import java.util.stream.*;

public class CollectionWithLambda {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
        
        // 使用Lambda进行遍历
        names.forEach(name -> System.out.println(name));
        
        // 使用方法引用
        names.forEach(System.out::println);
        
        // 使用Stream API和Lambda进行过滤和映射
        List<String> filteredNames = names.stream()
            .filter(name -> name.length() > 4)
            .map(String::toUpperCase)
            .collect(Collectors.toList());
        
        System.out.println(filteredNames); // [ALICE, CHARLIE, DAVID]
    }
}

8.2 线程创建

java 复制代码
public class ThreadWithLambda {
    public static void main(String[] args) {
        // 传统方式
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("传统方式创建的线程");
            }
        }).start();
        
        // 使用Lambda表达式
        new Thread(() -> System.out.println("Lambda方式创建的线程")).start();
    }
}

8.3 排序操作

java 复制代码
import java.util.*;

public class SortWithLambda {
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
            new Person("Alice", 25),
            new Person("Bob", 30),
            new Person("Charlie", 20)
        );
        
        // 使用Lambda表达式按年龄排序
        Collections.sort(people, (p1, p2) -> p1.getAge() - p2.getAge());
        
        // 使用方法引用
        Collections.sort(people, Comparator.comparing(Person::getAge));
        
        people.forEach(System.out::println);
    }
}

class Person {
    private String name;
    private int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    // getters and toString
    public String getName() { return name; }
    public int getAge() { return age; }
    
    @Override
    public String toString() {
        return name + "(" + age + ")";
    }
}

8.4 使用建议

  • 应该使用Lambda的场景

    java 复制代码
    // 1. 简单的集合操作
    list.stream().filter(s -> s != null).forEach(System.out::println);
    
    // 2. 事件处理
    button.addActionListener(e -> handleButtonClick());
    
    // 3. 简单的线程任务
    new Thread(() -> doBackgroundWork()).start();
    
    // 4. 函数式接口实现
    Comparator<String> comparator = (s1, s2) -> s1.compareTo(s2);
  • 应该避免使用Lambda的场景

    java 复制代码
    // 1. 复杂的业务逻辑
    // 不好的做法:
    list.stream().map(item -> {
        // 几十行复杂逻辑
        if (condition1) {
            // 复杂处理...
        } else if (condition2) {
            // 更复杂处理...
        }
        return result;
    });
    
    // 好的做法:提取方法
    list.stream().map(this::processComplexBusinessLogic);
    
    // 2. 需要多次重用的逻辑
    // 不好的做法:重复编写相同的Lambda
    list1.stream().filter(s -> s.length() > 5 && s.contains("abc"));
    list2.stream().filter(s -> s.length() > 5 && s.contains("abc"));
    
    // 好的做法:定义Predicate
    Predicate<String> complexFilter = s -> s.length() > 5 && s.contains("abc");
    list1.stream().filter(complexFilter);
    list2.stream().filter(complexFilter);

8.5 最佳实践

1. 保持Lambda简洁

java 复制代码
// 不好:复杂的Lambda
list.stream().map(s -> {
    String trimmed = s.trim();
    if (trimmed.isEmpty()) return "DEFAULT";
    return trimmed.toUpperCase();
});

// 好:使用方法引用和简单Lambda
list.stream()
    .map(String::trim)
    .map(s -> s.isEmpty() ? "DEFAULT" : s)
    .map(String::toUpperCase);

2. 使用方法引用

java 复制代码
// 使用Lambda
list.stream().map(s -> s.toUpperCase());

// 使用方法引用(更好)
list.stream().map(String::toUpperCase);

3. 限制Lambda长度

java 复制代码
// 如果Lambda超过3行,考虑提取方法
list.stream().map(item -> {
    // 如果逻辑复杂,提取到方法中
    return processItem(item);
});

private ResultType processItem(ItemType item) {
    // 复杂逻辑放在这里
}