MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Java Stream 多方法组合处理数据

2022-07-195.7k 阅读

Java Stream 概述

Java 8 引入了 Stream API,它为处理集合数据提供了一种更为高效和简洁的方式。Stream 不是数据结构,它并不存储数据,而是在数据源(如集合、数组等)上执行一系列操作。Stream 操作可以分为中间操作和终端操作,中间操作返回一个新的 Stream,允许链式调用多个中间操作;终端操作执行 Stream 操作链,并返回结果或副作用。

Stream 创建

  1. 从集合创建 集合接口都有 stream() 和 parallelStream() 方法。stream() 方法创建顺序流,parallelStream() 创建并行流。例如:
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class StreamCreationFromCollection {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(1);
        numbers.add(2);
        numbers.add(3);

        // 创建顺序流
        Stream<Integer> sequentialStream = numbers.stream();

        // 创建并行流
        Stream<Integer> parallelStream = numbers.parallelStream();
    }
}
  1. 从数组创建 可以使用 Arrays.stream() 方法从数组创建 Stream。
import java.util.Arrays;
import java.util.stream.Stream;

public class StreamCreationFromArray {
    public static void main(String[] args) {
        int[] intArray = {1, 2, 3};
        Stream<int[]> streamOfArrays = Stream.of(intArray);

        // 基本类型特化流
        java.util.stream.IntStream intStream = Arrays.stream(intArray);
    }
}
  1. 使用 Stream.of() 可以直接使用 Stream.of() 方法创建 Stream,传入可变参数。
import java.util.stream.Stream;

public class StreamCreationWithOf {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("a", "b", "c");
    }
}

中间操作

  1. 过滤 (filter) filter 方法用于根据条件过滤 Stream 中的元素。它接受一个 Predicate 作为参数,该 Predicate 对每个元素进行判断,返回 true 的元素会被保留在新的 Stream 中。
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class StreamFilter {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(1);
        numbers.add(2);
        numbers.add(3);
        numbers.add(4);

        List<Integer> filteredNumbers = numbers.stream()
               .filter(n -> n % 2 == 0)
               .collect(Collectors.toList());

        System.out.println(filteredNumbers); // 输出: [2, 4]
    }
}
  1. 映射 (map) map 方法将 Stream 中的每个元素按照给定的 Function 进行转换,生成一个新的 Stream。例如,将字符串转换为其长度。
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class StreamMap {
    public static void main(String[] args) {
        List<String> words = new ArrayList<>();
        words.add("apple");
        words.add("banana");
        words.add("cherry");

        List<Integer> wordLengths = words.stream()
               .map(String::length)
               .collect(Collectors.toList());

        System.out.println(wordLengths); // 输出: [5, 6, 6]
    }
}
  1. 扁平映射 (flatMap) flatMap 方法与 map 类似,但它用于处理嵌套结构。它将每个元素映射为一个 Stream,然后将这些 Stream 扁平化为一个单一的 Stream。例如,处理一个包含多个列表的列表。
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class StreamFlatMap {
    public static void main(String[] args) {
        List<List<Integer>> nestedLists = new ArrayList<>();
        List<Integer> list1 = new ArrayList<>();
        list1.add(1);
        list1.add(2);
        List<Integer> list2 = new ArrayList<>();
        list2.add(3);
        list2.add(4);
        nestedLists.add(list1);
        nestedLists.add(list2);

        List<Integer> flatList = nestedLists.stream()
               .flatMap(List::stream)
               .collect(Collectors.toList());

        System.out.println(flatList); // 输出: [1, 2, 3, 4]
    }
}
  1. 排序 (sorted) sorted 方法用于对 Stream 中的元素进行排序。可以使用自然排序(如果元素实现了 Comparable 接口),也可以传入一个 Comparator 进行自定义排序。
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

public class StreamSorted {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(3);
        numbers.add(1);
        numbers.add(2);

        // 自然排序
        List<Integer> sortedNumbers = numbers.stream()
               .sorted()
               .collect(Collectors.toList());

        System.out.println(sortedNumbers); // 输出: [1, 2, 3]

        // 自定义排序
        List<Integer> reverseSortedNumbers = numbers.stream()
               .sorted(Comparator.reverseOrder())
               .collect(Collectors.toList());

        System.out.println(reverseSortedNumbers); // 输出: [3, 2, 1]
    }
}
  1. 去重 (distinct) distinct 方法用于去除 Stream 中的重复元素。它通过对象的 equals 方法来判断元素是否重复。
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class StreamDistinct {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(1);
        numbers.add(2);
        numbers.add(2);
        numbers.add(3);

        List<Integer> distinctNumbers = numbers.stream()
               .distinct()
               .collect(Collectors.toList());

        System.out.println(distinctNumbers); // 输出: [1, 2, 3]
    }
}
  1. 限制 (limit) limit 方法用于截取 Stream 中的前 n 个元素,返回一个新的 Stream。
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class StreamLimit {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(1);
        numbers.add(2);
        numbers.add(3);
        numbers.add(4);
        numbers.add(5);

        List<Integer> limitedNumbers = numbers.stream()
               .limit(3)
               .collect(Collectors.toList());

        System.out.println(limitedNumbers); // 输出: [1, 2, 3]
    }
}
  1. 跳过 (skip) skip 方法用于跳过 Stream 中的前 n 个元素,返回剩余元素组成的新 Stream。
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class StreamSkip {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(1);
        numbers.add(2);
        numbers.add(3);
        numbers.add(4);
        numbers.add(5);

        List<Integer> skippedNumbers = numbers.stream()
               .skip(2)
               .collect(Collectors.toList());

        System.out.println(skippedNumbers); // 输出: [3, 4, 5]
    }
}

终端操作

  1. 收集 (collect) collect 方法用于将 Stream 中的元素收集到一个集合中,或者生成一个汇总结果。Collectors 类提供了许多静态方法来支持不同类型的收集操作。
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class StreamCollect {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(1);
        numbers.add(2);
        numbers.add(3);
        numbers.add(4);

        // 收集到 List
        List<Integer> collectedList = numbers.stream()
               .collect(Collectors.toList());

        // 收集到 Set
        Set<Integer> collectedSet = numbers.stream()
               .collect(Collectors.toSet());

        // 按奇偶性分组
        Map<Boolean, List<Integer>> groupedByParity = numbers.stream()
               .collect(Collectors.groupingBy(n -> n % 2 == 0));
    }
}
  1. 归约 (reduce) reduce 方法用于将 Stream 中的元素进行累积操作,通过一个 BinaryOperator 来定义累积的逻辑。它有不同的重载形式,可以提供一个初始值。
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

public class StreamReduce {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(1);
        numbers.add(2);
        numbers.add(3);

        // 无初始值的归约
        Optional<Integer> sumWithoutIdentity = numbers.stream()
               .reduce((a, b) -> a + b);

        sumWithoutIdentity.ifPresent(System.out::println); // 输出: 6

        // 有初始值的归约
        int sumWithIdentity = numbers.stream()
               .reduce(10, (a, b) -> a + b);

        System.out.println(sumWithIdentity); // 输出: 16
    }
}
  1. 查找与匹配
    • anyMatch:判断 Stream 中是否至少有一个元素匹配给定的 Predicate。
    • allMatch:判断 Stream 中的所有元素是否都匹配给定的 Predicate。
    • noneMatch:判断 Stream 中是否没有元素匹配给定的 Predicate。
    • findFirst:返回 Stream 中的第一个元素,如果 Stream 为空则返回 Optional.empty。
    • findAny:返回 Stream 中的任意一个元素,如果 Stream 为空则返回 Optional.empty,在并行流中表现更高效。
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

public class StreamFindAndMatch {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(1);
        numbers.add(2);
        numbers.add(3);

        boolean anyMatch = numbers.stream()
               .anyMatch(n -> n % 2 == 0);

        boolean allMatch = numbers.stream()
               .allMatch(n -> n > 0);

        boolean noneMatch = numbers.stream()
               .noneMatch(n -> n < 0);

        Optional<Integer> first = numbers.stream()
               .findFirst();

        Optional<Integer> any = numbers.stream()
               .findAny();
    }
}
  1. 计数 (count) count 方法用于统计 Stream 中的元素个数,返回一个 long 类型的值。
import java.util.ArrayList;
import java.util.List;

public class StreamCount {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(1);
        numbers.add(2);
        numbers.add(3);

        long count = numbers.stream()
               .count();

        System.out.println(count); // 输出: 3
    }
}
  1. 最大值与最小值 (max/min) max 和 min 方法分别用于获取 Stream 中的最大值和最小值,需要传入一个 Comparator 来定义比较逻辑。
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;

public class StreamMaxMin {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(1);
        numbers.add(2);
        numbers.add(3);

        Optional<Integer> max = numbers.stream()
               .max(Comparator.naturalOrder());

        Optional<Integer> min = numbers.stream()
               .min(Comparator.naturalOrder());
    }
}

多方法组合处理数据

  1. 复杂过滤与映射组合 假设我们有一个包含人员信息的列表,每个人员有姓名、年龄和性别。我们想筛选出年龄大于 18 岁的女性,并将她们的姓名转换为大写。
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

class Person {
    private String name;
    private int age;
    private String gender;

    public Person(String name, int age, String gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public String getGender() {
        return gender;
    }
}

public class ComplexStreamOperations {
    public static void main(String[] args) {
        List<Person> people = new ArrayList<>();
        people.add(new Person("Alice", 20, "Female"));
        people.add(new Person("Bob", 15, "Male"));
        people.add(new Person("Eve", 25, "Female"));

        List<String> filteredAndMappedNames = people.stream()
               .filter(p -> p.getAge() > 18 && "Female".equals(p.getGender()))
               .map(Person::getName)
               .map(String::toUpperCase)
               .collect(Collectors.toList());

        System.out.println(filteredAndMappedNames); // 输出: [ALICE, EVE]
    }
}
  1. 排序、过滤与收集组合 对于一个包含整数的列表,我们先对其进行排序,然后过滤掉小于 5 的数,最后收集到一个新的列表中。
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class SortFilterCollect {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(3);
        numbers.add(7);
        numbers.add(2);
        numbers.add(8);

        List<Integer> result = numbers.stream()
               .sorted()
               .filter(n -> n >= 5)
               .collect(Collectors.toList());

        System.out.println(result); // 输出: [7, 8]
    }
}
  1. 扁平映射、过滤与归约组合 假设有一个二维数组,我们先将其扁平化为一维 Stream,然后过滤掉负数,最后计算剩余数字的总和。
import java.util.Arrays;
import java.util.stream.IntStream;

public class FlatMapFilterReduce {
    public static void main(String[] args) {
        int[][] arrays = {{1, -2, 3}, {-4, 5, 6}};

        int sum = Arrays.stream(arrays)
               .flatMapToInt(IntStream::of)
               .filter(n -> n > 0)
               .reduce(0, (a, b) -> a + b);

        System.out.println(sum); // 输出: 15
    }
}

并行流的多方法组合

并行流利用多核处理器的优势,提高大数据量处理的效率。在进行多方法组合时,并行流的中间操作和终端操作与顺序流类似,但执行方式不同。

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

public class ParallelStreamCombination {
    public static void main(String[] args) {
        List<Integer> largeList = new ArrayList<>();
        for (int i = 0; i < 1000000; i++) {
            largeList.add(i);
        }

        List<Integer> result = largeList.parallelStream()
               .filter(n -> n % 2 == 0)
               .map(n -> n * 2)
               .sorted()
               .collect(Collectors.toList());
    }
}

在上述示例中,我们将一个包含一百万个整数的列表转换为并行流,然后依次进行过滤、映射和排序操作,最后收集结果。并行流会自动将数据分成多个部分,在不同的线程中并行处理这些操作,从而提高整体处理速度。

然而,使用并行流时需要注意一些问题。例如,某些操作在并行流中可能会有性能损耗,如状态相关的操作(如使用有状态的 Collector)。此外,并行流依赖于 Fork/Join 框架,任务划分和合并的开销也需要考虑。在实际应用中,需要根据数据量和操作的复杂度来权衡是否使用并行流。

总结 Stream 多方法组合的优势

  1. 代码简洁性:通过链式调用多个方法,避免了复杂的循环嵌套和临时变量的声明,使代码更易读和维护。
  2. 功能强大:可以组合多种中间操作和终端操作,实现复杂的数据处理逻辑,如数据清洗、转换、聚合等。
  3. 并行处理:支持并行流,能够充分利用多核处理器的性能,提高大数据量处理的效率。
  4. 声明式编程:Stream API 采用声明式编程风格,只需描述要做什么,而不是如何做,让开发者更专注于业务逻辑。

在实际的 Java 开发中,Stream 多方法组合是处理集合数据的强大工具,无论是在数据处理、数据分析还是其他相关领域,都能发挥重要作用,提升代码的质量和性能。