Java 8 Lambda表达式详解 - 实践

一、什么是Lambda表达式

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

二、Lambda表达式语法

2.1 基本语法

(parameters) -> expression

或者

(parameters) -> { statements; }

2.2 语法组成

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

2.3 Lambda表达式示例

// 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表达式可以替代匿名内部类,减少模板代码,使代码更清晰。

    // 传统方式
    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可以写出更声明式的代码。支持将函数作为参数传递,为设计更抽象的代码提供了可能。

    // 函数组合示例
    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结合,可以更容易地编写并行代码,提高多核处理器上的性能。

    // 串行处理
    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更加强大和灵活,例如集合框架的批量操作。

    // 更灵活的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表达式的堆栈跟踪可能比传统方法复杂,给调试带来一定困难。

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

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

    // 复杂的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)。

    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中的异常处理比较麻烦,需要包装受检异常。

    // 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注解标记。
// 自定义函数式接口
@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
}
}

内置的函数式接口:

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表达式的一种简写形式。方法引用的四种形式:

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表达式可以捕获外部变量,但有一些限制:

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() - 过滤

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

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() - 映射转换

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

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。

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 方法判断。

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() - 排序

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

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() - 限制和跳过

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() - 查看元素(调试用)

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

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() - 遍历

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

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 等。

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 中的元素组合起来,得到一个结果。

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() - 计数

计算元素个数。

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() - 匹配检查

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() - 查找元素

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. 数值流特化方法

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

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. 并行流处理

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 集合操作

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 线程创建

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 排序操作

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的场景

    // 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的场景

    // 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简洁

// 不好:复杂的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. 使用方法引用

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

3. 限制Lambda长度

// 如果Lambda超过3行,考虑提取方法
list.stream().map(item -> {
// 如果逻辑复杂,提取到方法中
return processItem(item);
});
private ResultType processItem(ItemType item) {
// 复杂逻辑放在这里
}
posted @ 2025-12-16 17:06  gccbuaa  阅读(20)  评论(0)    收藏  举报