V
Vel·ToolKit
简洁 · 高效 · 即开即用
ZH
第 14 章 / 共 20 章

Lambda 与 Stream API

lambda、方法引用、Stream 链式、Collectors、并行流

为什么用 Lambda 与 Stream

应用场景:处理集合 / 流式数据时,传统 for 循环 + 临时变量的写法又长又容易出错。Lambda + Stream 让你用"说明意图"的方式写:过滤、映射、分组、聚合都是一行操作,更接近数学公式而不是步骤。

// WhyStream.java
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class WhyStream {
    record User(String name, int age, String dept) {}

    public static void main(String[] args) {
        List<User> users = List.of(
            new User("Alice", 30, "Eng"),
            new User("Bob",   25, "Sales"),
            new User("Carol", 35, "Eng"),
            new User("Dave",  40, "Sales")
        );

        // 命令式:找 Eng 部门、年龄 >= 30 的名字
        List<String> imperative = new ArrayList<>();
        for (User u : users) {
            if (u.dept().equals("Eng") && u.age() >= 30) {
                imperative.add(u.name());
            }
        }
        System.out.println(imperative);

        // 声明式:用 Stream,一目了然
        List<String> declarative = users.stream()
            .filter(u -> u.dept().equals("Eng"))
            .filter(u -> u.age() >= 30)
            .map(User::name)
            .collect(Collectors.toList());
        System.out.println(declarative);
    }
}

Lambda 表达式语法

lambda 是函数式接口(单抽象方法接口)的简写。形式:(参数列表) -> 表达式 或 (参数列表) -> { 语句块; return 值; }。参数类型一般可省,编译器从上下文推断。

// LambdaSyntax.java
import java.util.function.*;

public class LambdaSyntax {
    public static void main(String[] args) {
        // 无参
        Supplier<String> greet = () -> "hello";
        System.out.println(greet.get());

        // 单参(可省括号)
        Function<Integer, Integer> square = x -> x * x;
        System.out.println(square.apply(5));

        // 多参
        BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
        System.out.println(add.apply(2, 3));

        // 语句块
        Predicate<String> validEmail = s -> {
            if (s == null) return false;
            int at = s.indexOf('@');
            return at > 0 && at < s.length() - 1;
        };
        System.out.println(validEmail.test("a@b.c"));
    }
}

方法引用四种形态

当 lambda 体只是"直接调用一个已有方法"时,可以写成方法引用,更简洁。

// MethodRef.java
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;

public class MethodRef {
    public static void main(String[] args) {
        // 1) 类的静态方法:Integer::parseInt 等价于 s -> Integer.parseInt(s)
        Function<String, Integer> parse = Integer::parseInt;
        System.out.println(parse.apply("42"));

        // 2) 实例的实例方法:System.out::println
        List<String> names = List.of("a", "b", "c");
        names.forEach(System.out::println);

        // 3) 类的实例方法:String::toUpperCase 等价于 s -> s.toUpperCase()
        Function<String, String> upper = String::toUpperCase;
        System.out.println(upper.apply("hi"));

        // 4) 构造方法引用:ArrayList::new
        Supplier<java.util.ArrayList<String>> factory = java.util.ArrayList::new;
        System.out.println(factory.get());
    }
}

Stream 创建

Stream 是"一次性"的:终止操作后就不能再用。常见创建方式:从集合 stream() / Arrays.stream / Stream.of / Stream.generate(无限流,需 limit)/ IntStream.range(整数区间)。

// StreamCreate.java
import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class StreamCreate {
    public static void main(String[] args) {
        // 1) 从集合
        List<Integer> list = List.of(1, 2, 3);
        list.stream().forEach(System.out::println);

        // 2) 从数组
        int[] arr = {1, 2, 3};
        Arrays.stream(arr).forEach(System.out::println);

        // 3) Stream.of
        Stream.of("a", "b", "c").forEach(System.out::println);

        // 4) 数字区间(左闭右开)
        IntStream.range(0, 5).forEach(System.out::println);

        // 5) 无限流 + limit
        Stream.iterate(1, n -> n * 2).limit(5).forEach(System.out::println);
        // 1 2 4 8 16
    }
}

中间操作:filter / map / flatMap / sorted / distinct / limit

中间操作返回新的 Stream,可链式调用。它们是"懒"的——只有遇到终止操作才会真正执行。

// StreamIntermediate.java
import java.util.List;
import java.util.stream.Collectors;

public class StreamIntermediate {
    public static void main(String[] args) {
        List<String> words = List.of("apple", "banana", "cherry", "avocado", "berry");

        // filter:保留满足条件的
        List<String> withA = words.stream()
            .filter(w -> w.startsWith("a"))
            .collect(Collectors.toList());
        System.out.println(withA);

        // map:每个元素做转换
        List<Integer> lengths = words.stream()
            .map(String::length)
            .collect(Collectors.toList());
        System.out.println(lengths);

        // flatMap:每个元素变成一个 Stream,再压平
        List<List<Integer>> nested = List.of(List.of(1, 2), List.of(3, 4));
        List<Integer> flat = nested.stream()
            .flatMap(List::stream)
            .collect(Collectors.toList());
        System.out.println(flat); // [1, 2, 3, 4]

        // sorted + distinct + limit 组合
        List<Integer> top3 = List.of(3, 1, 4, 1, 5, 9, 2, 6, 5).stream()
            .distinct()
            .sorted(java.util.Comparator.reverseOrder())
            .limit(3)
            .collect(Collectors.toList());
        System.out.println(top3); // [9, 6, 5]
    }
}

终止操作:forEach / collect / reduce / count / anyMatch / findFirst

终止操作触发计算,并消耗这个 Stream(之后不能再用)。返回的不再是 Stream,而是具体的值/集合/Optional。

// StreamTerminal.java
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

public class StreamTerminal {
    public static void main(String[] args) {
        List<Integer> nums = List.of(1, 2, 3, 4, 5);

        // count
        long even = nums.stream().filter(n -> n % 2 == 0).count();
        System.out.println("even count = " + even);

        // anyMatch / allMatch / noneMatch
        System.out.println(nums.stream().anyMatch(n -> n > 3));   // true
        System.out.println(nums.stream().allMatch(n -> n > 0));   // true
        System.out.println(nums.stream().noneMatch(n -> n < 0));  // true

        // findFirst:返回 Optional
        Optional<Integer> first = nums.stream().filter(n -> n > 3).findFirst();
        System.out.println(first.orElse(-1));

        // reduce:归约
        int sum = nums.stream().reduce(0, Integer::sum);
        System.out.println("sum = " + sum);

        // collect 到 List
        List<Integer> doubled = nums.stream()
            .map(n -> n * 2)
            .collect(Collectors.toList());
        System.out.println(doubled);
    }
}

Collectors:分组、统计、拼接

Collectors 是 Stream.collect 的"插件",提供分组(groupingBy)、按 key 收成 Map(toMap)、字符串拼接(joining)、统计(counting / summingInt / averagingDouble)等终止操作。**实际开发用 Stream 最多的就是这块。**

// CollectorsDemo.java
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class CollectorsDemo {
    record Order(String user, String product, int amount) {}

    public static void main(String[] args) {
        List<Order> orders = List.of(
            new Order("Alice", "book",   30),
            new Order("Bob",   "book",   25),
            new Order("Alice", "pen",    10),
            new Order("Carol", "book",   60),
            new Order("Bob",   "pen",     5)
        );

        // 1) 拼接:所有用户名(去重 + 逗号)
        String names = orders.stream()
            .map(Order::user)
            .distinct()
            .collect(Collectors.joining(", "));
        System.out.println(names); // Alice, Bob, Carol

        // 2) 按用户分组
        Map<String, List<Order>> byUser = orders.stream()
            .collect(Collectors.groupingBy(Order::user));
        System.out.println(byUser);

        // 3) 按用户聚合总金额
        Map<String, Integer> totalByUser = orders.stream()
            .collect(Collectors.groupingBy(
                Order::user,
                Collectors.summingInt(Order::amount)
            ));
        System.out.println(totalByUser); // {Alice=40, Bob=30, Carol=60}

        // 4) 按产品计数
        Map<String, Long> countByProduct = orders.stream()
            .collect(Collectors.groupingBy(
                Order::product,
                Collectors.counting()
            ));
        System.out.println(countByProduct);

        // 5) 用户名 -> 总金额(toMap)
        Map<String, Integer> totalMap = orders.stream()
            .collect(Collectors.toMap(
                Order::user,
                Order::amount,
                Integer::sum   // 同一个 key 多次出现时合并函数
            ));
        System.out.println(totalMap);
    }
}

IntStream / LongStream / DoubleStream

数字专用流,避免装箱拆箱开销。提供 sum / average / max / min / summaryStatistics 这些数值聚合方法。

// NumericStream.java
import java.util.IntSummaryStatistics;
import java.util.stream.IntStream;

public class NumericStream {
    public static void main(String[] args) {
        int sum = IntStream.rangeClosed(1, 100).sum();
        System.out.println("1..100 sum = " + sum); // 5050

        IntSummaryStatistics stats = IntStream.of(3, 1, 4, 1, 5, 9, 2, 6).summaryStatistics();
        System.out.println(stats);
        // IntSummaryStatistics{count=8, sum=31, min=1, average=3.875000, max=9}

        // 普通 Stream<Integer> 转 IntStream
        int total = java.util.List.of(1, 2, 3).stream().mapToInt(Integer::intValue).sum();
        System.out.println(total);
    }
}

并行流:parallelStream() 何时用

调 .parallelStream() 或 .parallel() 让数据被多核同时处理。**注意:不是越快越好。**只在数据量大(万级以上)、操作 CPU 密集、操作无状态时用;涉及 IO / 顺序敏感 / 共享可变状态时反而更慢更危险。

// Parallel.java
import java.util.stream.IntStream;

public class Parallel {
    public static void main(String[] args) {
        // 计算 1..1_000_000 平方和
        long sum = IntStream.rangeClosed(1, 1_000_000)
            .parallel()
            .mapToLong(n -> (long) n * n)
            .sum();
        System.out.println(sum);
    }
}

实战:从订单聚合 Top-3 用户

组合 filter + groupingBy + summingInt + sorted + limit 的常见业务场景:从订单数据找消费 Top-3 用户。

// TopUsers.java
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class TopUsers {
    record Order(String user, String product, int amount, String status) {}

    public static void main(String[] args) {
        List<Order> orders = List.of(
            new Order("Alice", "book",  30, "paid"),
            new Order("Bob",   "book",  25, "paid"),
            new Order("Alice", "pen",   10, "refund"),
            new Order("Carol", "book",  60, "paid"),
            new Order("Dave",  "pen",   90, "paid"),
            new Order("Bob",   "pen",    5, "paid")
        );

        // Top-3 已支付用户消费额
        List<Map.Entry<String, Integer>> top3 = orders.stream()
            .filter(o -> o.status().equals("paid"))
            .collect(Collectors.groupingBy(Order::user, Collectors.summingInt(Order::amount)))
            .entrySet().stream()
            .sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
            .limit(3)
            .collect(Collectors.toList());

        top3.forEach(e -> System.out.println(e.getKey() + " -> " + e.getValue()));
        // Dave -> 90, Carol -> 60, Bob -> 30
    }
}