Java Stream流 带你从入门到精通,解锁高效数据处理新姿势。

一、Stream流概述

Java 8引入的Stream API是处理集合数据的革命性工具,它允许我们以声明式方式处理数据,并支持并行操作。Stream不是数据结构,而是对数据源(集合、数组等)的高级抽象,可以让我们写出更简洁、更易读的代码。

Stream与传统集合操作对比

  1. 内部迭代:Stream使用内部迭代(由库控制),而集合使用外部迭代(程序员控制)
  2. 惰性求值:许多Stream操作是惰性的,可以提高性能
  3. 函数式风格:支持lambda表达式和方法引用
  4. 可并行:只需调用parallel()就能实现并行处理

二、Stream创建方式

1. 从集合创建

List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();        // 顺序流
Stream<String> parallelStream = list.parallelStream(); // 并行流

2. 从数组创建

String[] array = {"a", "b", "c"};
Stream<String> stream = Arrays.stream(array);

3. 使用Stream.of()

Stream<String> stream = Stream.of("a", "b", "c");

4. 使用Stream.generate()创建无限流

Stream<Double> randomStream = Stream.generate(Math::random).limit(5);
randomStream.forEach(System.out::println);

5. 使用Stream.iterate()创建无限流

Stream<Integer> evenNumStream = Stream.iterate(0, n -> n + 2).limit(5);
evenNumStream.forEach(System.out::println);

6. 其他创建方式

// 空流
Stream<String> emptyStream = Stream.empty();

// 基本类型流
IntStream intStream = IntStream.range(1, 5); // 1,2,3,4
LongStream longStream = LongStream.rangeClosed(1, 5); // 1,2,3,4,5

三、Stream中间操作

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

1. filter() - 过滤

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
List<Integer> evenNumbers = numbers.stream()
                                  .filter(n -> n % 2 == 0)
                                  .collect(Collectors.toList());
// 结果: [2, 4, 6]

2. map() - 映射

List<String> words = Arrays.asList("hello", "world");
List<Integer> wordLengths = words.stream()
                                .map(String::length)
                                .collect(Collectors.toList());
// 结果: [5, 5]

3. flatMap() - 扁平化映射

List<List<String>> listOfLists = Arrays.asList(
    Arrays.asList("a", "b"),
    Arrays.asList("c", "d")
);
List<String> flatList = listOfLists.stream()
                                  .flatMap(List::stream)
                                  .collect(Collectors.toList());
// 结果: ["a", "b", "c", "d"]

4. distinct() - 去重

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

5. sorted() - 排序

List<String> words = Arrays.asList("banana", "apple", "pear");
List<String> sortedWords = words.stream()
                               .sorted()
                               .collect(Collectors.toList());
// 结果: ["apple", "banana", "pear"]

// 自定义排序
List<String> customSorted = words.stream()
                                .sorted(Comparator.comparing(String::length).reversed())
                                .collect(Collectors.toList());
// 结果: ["banana", "apple", "pear"]

6. limit()和skip() - 截取和跳过

List<Integer> numbers = IntStream.range(1, 10).boxed().collect(Collectors.toList());
List<Integer> limited = numbers.stream().limit(5).collect(Collectors.toList());
// 结果: [1, 2, 3, 4, 5]

List<Integer> skipped = numbers.stream().skip(5).collect(Collectors.toList());
// 结果: [6, 7, 8, 9]

7. peek() - 查看流中元素

List<String> result = Stream.of("one", "two", "three")
                           .filter(s -> s.length() > 3)
                           .peek(s -> System.out.println("Filtered value: " + s))
                           .map(String::toUpperCase)
                           .peek(s -> System.out.println("Mapped value: " + s))
                           .collect(Collectors.toList());

四、Stream终端操作

终端操作会消耗流,产生一个结果或副作用。

1. forEach() - 遍历

List<String> words = Arrays.asList("hello", "world");
words.stream().forEach(System.out::println);

2. collect() - 收集为集合

List<String> collected = Stream.of("a", "b", "c")
                              .collect(Collectors.toList());

// 转为Set
Set<String> set = Stream.of("a", "b", "a")
                       .collect(Collectors.toSet());
// 结果: ["a", "b"]

// 转为Map
Map<String, Integer> map = Stream.of("apple", "banana", "pear")
                                .collect(Collectors.toMap(
                                    Function.identity(), // key
                                    String::length      // value
                                ));
// 结果: {apple=5, banana=6, pear=4}

3. reduce() - 归约

// 求和
Optional<Integer> sum = Stream.of(1, 2, 3, 4).reduce(Integer::sum);
// 结果: Optional[10]

// 使用初始值
Integer sumWithIdentity = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);
// 结果: 10

// 字符串连接
String concatenated = Stream.of("a", "b", "c").reduce("", String::concat);
// 结果: "abc"

4. min()/max() - 最小/最大值

Optional<String> min = Stream.of("banana", "apple", "pear")
                            .min(Comparator.naturalOrder());
// 结果: Optional["apple"]

Optional<Integer> max = Stream.of(1, 2, 3, 4).max(Integer::compare);
// 结果: Optional[4]

5. count() - 计数

long count = Stream.of("a", "b", "c").count();
// 结果: 3

6. anyMatch()/allMatch()/noneMatch() - 匹配检查

boolean anyStartsWithA = Stream.of("apple", "banana", "pear")
                              .anyMatch(s -> s.startsWith("a"));
// 结果: true

boolean allLongerThan3 = Stream.of("apple", "banana", "pear")
                              .allMatch(s -> s.length() > 3);
// 结果: true

boolean noneStartsWithZ = Stream.of("apple", "banana", "pear")
                               .noneMatch(s -> s.startsWith("z"));
// 结果: true

7. findFirst()/findAny() - 查找元素

Optional<String> first = Stream.of("apple", "banana", "pear")
                              .findFirst();
// 结果: Optional["apple"]

Optional<String> any = Stream.of("apple", "banana", "pear")
                            .parallel()  // 并行流中findAny可能更快
                            .findAny();
// 结果可能是Optional["apple"], Optional["banana"]或Optional["pear"]

五、数值流特化

Java 8引入了三种原始类型特化流:IntStream、LongStream和DoubleStream,避免装箱拆箱开销。

1. 创建数值流

IntStream intStream = IntStream.range(1, 5);       // 1,2,3,4
IntStream closedIntStream = IntStream.rangeClosed(1, 5); // 1,2,3,4,5

// 将对象流转为数值流
List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
IntStream intStreamFromObj = numbers.stream().mapToInt(Integer::intValue);

2. 常用数值流操作

// 求和
int sum = IntStream.range(1, 5).sum();  // 10

// 平均值
OptionalDouble avg = IntStream.range(1, 5).average(); // OptionalDouble[2.5]

// 最大值
OptionalInt max = IntStream.range(1, 5).max(); // OptionalInt[4]

// 统计信息
IntSummaryStatistics stats = IntStream.range(1, 5).summaryStatistics();
// stats: IntSummaryStatistics{count=4, sum=10, min=1, average=2.5, max=4}

3. 数值流转回对象流

Stream<Integer> boxed = IntStream.range(1, 5).boxed();

六、并行流

Stream API可以轻松实现并行处理。

1. 创建并行流

// 方式1: 从集合创建
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> parallelStream = list.parallelStream();

// 方式2: 将顺序流转为并行流
Stream<String> parallel = Stream.of("a", "b", "c").parallel();

2. 并行流注意事项

// 错误示例: 共享可变状态
List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
List<Integer> badParallelResult = new ArrayList<>();
numbers.parallelStream()
       .map(n -> n * 2)
       .forEach(badParallelResult::add); // 可能抛出ArrayIndexOutOfBoundsException

// 正确做法: 使用线程安全的收集器
List<Integer> goodParallelResult = numbers.parallelStream()
                                         .map(n -> n * 2)
                                         .collect(Collectors.toList());

3. 并行流性能考虑

  • 数据量大时(通常超过1万元素)才考虑使用并行流
  • 考虑操作本身的并行成本(如NQ模型)
  • 避免在并行流中使用有状态的操作(如sorted())
  • 注意线程安全问题

七、高级收集器

Collectors类提供了丰富的收集器实现。

1. 分组

class Person {
    String name;
    String city;
    int age;
    
    // 构造方法、getter/setter省略
}

List<Person> people = Arrays.asList(
    new Person("Alice", "New York", 25),
    new Person("Bob", "New York", 30),
    new Person("Charlie", "London", 28)
);

// 简单分组
Map<String, List<Person>> peopleByCity = people.stream()
    .collect(Collectors.groupingBy(Person::getCity));
// 结果: {"New York":[Alice, Bob], "London":[Charlie]}

// 多级分组
Map<String, Map<Integer, List<Person>>> peopleByCityAndAge = people.stream()
    .collect(Collectors.groupingBy(Person::getCity,
        Collectors.groupingBy(Person::getAge)));

2. 分区

// 分区是分组的特殊情况,谓词只有true/false两种情况
Map<Boolean, List<Person>> partitioned = people.stream()
    .collect(Collectors.partitioningBy(p -> p.getAge() > 27));
// 结果: {false=[Alice], true=[Bob, Charlie]}

3. 连接字符串

String joined = people.stream()
    .map(Person::getName)
    .collect(Collectors.joining(", "));
// 结果: "Alice, Bob, Charlie"

4. 汇总统计

IntSummaryStatistics ageStats = people.stream()
    .collect(Collectors.summarizingInt(Person::getAge));
// 结果: IntSummaryStatistics{count=3, sum=83, min=25, average=27.666667, max=30}

5. 自定义收集器

// 实现Collector接口可以创建自定义收集器
Collector<Person, ?, TreeSet<Person>> toTreeSet = Collector.of(
    TreeSet::new,
    TreeSet::add,
    (left, right) -> { left.addAll(right); return left; },
    Collector.Characteristics.UNORDERED
);

TreeSet<Person> personTreeSet = people.stream().collect(toTreeSet);

八、实战案例

案例1:统计单词频率

String sentence = "hello world hello java world stream";
Map<String, Long> wordCount = Arrays.stream(sentence.split(" "))
                                   .collect(Collectors.groupingBy(
                                       Function.identity(),
                                       Collectors.counting()
                                   ));
// 结果: {hello=2, world=2, java=1, stream=1}

案例2:找出最贵的商品

class Product {
    String name;
    double price;
    // 构造方法、getter/setter省略
}

List<Product> products = Arrays.asList(
    new Product("Laptop", 999.99),
    new Product("Phone", 699.99),
    new Product("Tablet", 499.99)
);

Optional<Product> mostExpensive = products.stream()
                                        .max(Comparator.comparing(Product::getPrice));
// 结果: Optional[Product{name='Laptop', price=999.99}]

案例3:数据转换和处理

List<String> transactions = Arrays.asList(
    "1001,2023-01-01,150.00",
    "1002,2023-01-02,75.50",
    "1003,2023-01-01,225.75"
);

// 解析并计算1月1日的总交易额
double totalJan1 = transactions.stream()
    .map(t -> t.split(","))
    .filter(parts -> parts[1].equals("2023-01-01"))
    .mapToDouble(parts -> Double.parseDouble(parts[2]))
    .sum();
// 结果: 375.75

九、Stream性能优化

  1. 避免装箱拆箱:使用原始类型特化流(IntStream等)
  2. 短路操作:尽早使用filter()减少后续处理量
  3. 顺序与并行选择:根据数据量和操作复杂度选择
  4. 避免副作用:保持lambda表达式的纯粹性
  5. 重用流:流一旦被消费就不能重用,需要重新创建

十、总结

Java Stream API提供了一种高效、声明式处理数据的方式。通过本文的学习,你应该已经掌握了:

  1. Stream的创建方式
  2. 丰富的中间操作和终端操作
  3. 并行流的使用和注意事项
  4. 强大的收集器功能
  5. 实际应用场景和性能优化技巧

Stream不仅让代码更简洁,还能利用多核处理器提高性能。但要注意,不是所有场景都适合使用Stream,应根据具体情况选择最合适的工具。

附录:常见问题解答

Q: Stream和Collection有什么区别?
A: Collection主要关注数据存储,而Stream关注数据处理。Collection是内存中的数据结构,Stream不是。

Q: 什么时候应该使用并行流?
A: 当处理大量数据且操作可以并行化时,且需要考虑并行带来的开销是否值得。

Q: Stream可以重用吗?
A: 不可以。Stream一旦被消费(终端操作执行)就不能再使用。

Q: Stream会修改源数据吗?
A: 不会。Stream操作不会修改源数据,而是产生新的Stream或结果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值