CodeGym /课程 /JAVA 25 SELF /在集合与流(Stream)中使用 Lambda

在集合与流(Stream)中使用 Lambda

JAVA 25 SELF
第 48 级 , 课程 1
可用

1. Lambda 表达式与集合

回顾一下,在 Lambda 表达式出现之前我们是如何处理集合的。假设有一个字符串列表,我们想把它们打印到屏幕上:

List<String> list = Arrays.asList("Cat", "Dog", "Hedgehog");

for (String s : list) {
    System.out.println(s);
}

这很简单,但如果我们想从列表中删除所有空字符串,就不得不写带条件的循环,有时还要使用迭代器(否则会抛出 ConcurrentModificationException)。再比如,按字符串长度排序:

Collections.sort(list, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
});

哪怕是这样一个简单任务——也要写 5 行代码,加上一堆“噪声”括号。如何优化?答案你已经知道了:使用 Lambda 表达式。

在集合方法中使用 Lambda 表达式

自 Java 8 起,集合接口新增了接收函数式接口的方法——这意味着我们可以向其中传入 Lambda 表达式。最常用的有:

  • forEach(Consumer<T> action)
  • removeIf(Predicate<T> filter)
  • sort(Comparator<T> c)
  • replaceAll(UnaryOperator<T> operator)

示例:forEach

将列表的所有元素输出到屏幕(旧方式):

for (String s : list) {
    System.out.println(s);
}

现在——用 Lambda:

list.forEach(s -> System.out.println(s));

或者更短,如果你想装一把“Java 大师”:

list.forEach(System.out::println); // method reference,我们稍后再讲

示例:removeIf

删除列表中的所有空字符串:

List<String> animals = new ArrayList<>(Arrays.asList("Cat", "", "Dog", "Hedgehog", ""));
animals.removeIf(s -> s.isEmpty());
System.out.println(animals); // [Cat, Dog, Hedgehog]

示例:sort

按字符串长度对列表排序:

List<String> animals = Arrays.asList("Cat", "Dog", "Hedgehog", "Elephant");

animals.sort((a, b) -> a.length() - b.length());
System.out.println(animals); // [Cat, Dog, Hedgehog, Elephant]

示例:replaceAll

将所有字符串转换为大写:

List<String> animals = new ArrayList<>(Arrays.asList("cat", "dog", "hedgehog"));
animals.replaceAll(s -> s.toUpperCase());
System.out.println(animals); // [CAT, DOG, HEDGEHOG]

2. Stream API 与 Lambda 表达式

随着 Java 8 的发布,出现了强大的 Stream API,用于以函数式风格处理集合。通过方法链,流可以进行过滤、转换、排序、收集等操作。而这些方法都接收 Lambda 表达式!

重要:Stream API 的完整讲解稍后再展开,这里只给出一些基础示例,帮助理解 Lambda 的作用。

示例:过滤

只保留长度大于 3 的字符串:

List<String> animals = Arrays.asList("cat", "elephant", "eel", "crocodile");
animals.stream()
       .filter(s -> s.length() > 3)
       .forEach(System.out::println); // elephant, crocodile

示例:转换(map

把所有字符串变为大写:

List<String> animals = Arrays.asList("cat", "elephant", "hedgehog");
List<String> upper = animals.stream()
                            .map(s -> s.toUpperCase())
                            .collect(Collectors.toList());
System.out.println(upper); // [CAT, ELEPHANT, HEDGEHOG]

示例:排序

获得按长度排序的新列表(不改变原列表):

List<String> animals = Arrays.asList("cat", "elephant", "eel", "crocodile");
List<String> sorted = animals.stream()
                             .sorted((a, b) -> a.length() - b.length())
                             .collect(Collectors.toList());
System.out.println(sorted); // [cat, eel, elephant, crocodile]

3. 与匿名类的对比

让我们比较一下,同样的代码用匿名类和用 Lambda 分别是什么样子。

按字符串长度排序

匿名类:

animals.sort(new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
});

Lambda 表达式:

animals.sort((a, b) -> a.length() - b.length());

结论:
Lambda 表达式可以显著减少代码行数,让代码更易读。括号更少、噪声更少——更聚焦于核心逻辑!

删除空字符串

匿名类:

animals.removeIf(new Predicate<String>() {
    @Override
    public boolean test(String s) {
        return s.isEmpty();
    }
});

Lambda 表达式:

animals.removeIf(s -> s.isEmpty());

4. 实践:用 Lambda 操作用户列表的小练习

让我们在一个小程序中试试,用 Lambda 表达式操作用户列表。

示例 1:按年龄过滤用户

class User {
    String name;
    int age;
    User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return name + " (" + age + ")";
    }
}

List<User> users = Arrays.asList(
    new User("Alice", 17),
    new User("Bob", 25),
    new User("Charlie", 15)
);

users.stream()
     .filter(u -> u.age >= 18)
     .forEach(System.out::println); // Bob (25)

示例 2:按姓名排序用户

List<User> users = Arrays.asList(
    new User("Alice", 17),
    new User("Bob", 25),
    new User("Charlie", 15)
);

users.sort((u1, u2) -> u1.name.compareTo(u2.name));
System.out.println(users);
// [Alice (17), Bob (25), Charlie (15)]

示例 3:将用户列表转换为姓名列表

List<String> names = users.stream()
                          .map(u -> u.name)
                          .collect(Collectors.toList());
System.out.println(names); // [Alice, Bob, Charlie]

示例 4:删除所有未成年人

List<User> users = new ArrayList<>(Arrays.asList(
    new User("Alice", 17),
    new User("Bob", 25),
    new User("Charlie", 15)
));

users.removeIf(u -> u.age < 18);
System.out.println(users); // [Bob (25)]

5. 一些有用的细节

特性:作用域与“有效的 final”变量

Lambda 表达式可以使用外部方法中的变量,但前提是这些变量是 final 或“有效的 final”(即初始化后未被修改)。

int minAge = 18;
users.stream()
     .filter(u -> u.age >= minAge)
     .forEach(System.out::println);

如果你试图在 Lambda 使用之后再修改 minAge,编译器会报错。

表格:集合与流的常用方法及其 Lambda 用法

集合/流方法 用途 Lambda 形态 示例
forEach(Consumer<T>)
对每个元素执行操作
x -> ...
list.forEach(s -> ...)
removeIf(Predicate<T>)
按条件删除元素
x -> ...
list.removeIf(s -> ...)
sort(Comparator<T>)
对元素排序
(a, b) -> ...
list.sort((a, b) -> ...)
replaceAll(UnaryOperator<T>)
替换每个元素
x -> ...
list.replaceAll(s -> ...)
filter(Predicate<T>)
过滤流
x -> ...
stream.filter(s -> ...)
map(Function<T, R>)
映射/转换元素
x -> ...
stream.map(s -> ...)
forEach(Consumer<T>)
遍历流
x -> ...
stream.forEach(s -> ...)
sorted(Comparator<T>)
在流中排序
(a, b) -> ...
stream.sorted((a, b) -> ...)

7. 常见错误

错误 1:Lambda 过长。 如果你的 Lambda 里已经写了 5 行代码、条件、循环和 try-catch——很可能应该把逻辑提取到独立方法。Lambda 更适合短小的逻辑。

错误 2:在 Lambda 中使用可变的外部变量。 如果你试图在 Lambda 内修改外部方法中的变量(例如计数器),编译器不会允许。该变量必须是 final 或“有效的 final”。

错误 3:忘记集合/流的方法并不总是修改原集合。 例如,stream().filter(...) 并不会改变原有列表,而是返回一个新的流。如果你想得到集合,请使用 collect(Collectors.toList())

错误 4:Lambda 表达式的类型不匹配。 如果方法接收的是 Comparator<T>,而你传入的是只有一个参数的 Lambda(而不是两个),就会产生编译错误。

错误 5:过度嵌套导致可读性下降。 如果你有一连串 mapfilterforEach,并且每个 Lambda 里还有 Lambda——代码将变得很难读。这种情况下最好把表达式拆成若干步骤,或提取到方法中。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION