1. 什么是 Stream API?
Stream API 是用于处理惩罚数据序列的功能,提供了一种高效、清晰和声明性的处理惩罚方式。Stream 不会存储数据,它是对数据源的高级抽象,可以举行聚合操作(如过滤、映射、排序、归约等)。Stream 的核心优势包括:
- 支持链式调用,增强代码可读性。
- 惰性求值特性,避免不必要的计算。
- 并行处理惩罚能力,充分利用多核 CPU 提拔性能。
2. Stream API 底子
Stream 可以通过多种方式创建,常见的有以下几种:
- 从 Collection 或 List 中获取。
- 使用 Stream.of() 方法。
- 使用 Arrays.stream() 处理惩罚数组。
- 使用文件或 I/O 操作天生。
示例:
- List<String> stringList = Arrays.asList("Java", "Python", "C++", "JavaScript");
- // 从集合获取 Stream
- Stream<String> streamFromList = stringList.stream();
- // 使用 Stream.of()
- Stream<String> streamOfStrings = Stream.of("Hello", "World");
- // 使用 Arrays.stream()
- int[] intArray = {1, 2, 3, 4, 5};
- IntStream intStream = Arrays.stream(intArray);
复制代码 3. Stream API 常见操作详解
Stream API 提供了丰富的操作来处理惩罚数据序列,这些操作分为中间操作和停止操作。中间操作不会立即执行,它们是惰性的,只有在停止操作触发时才会执行。停止操作会结束流的处理惩罚并天生结果。以下是对 Stream API 常见操作的细化讲解,包括分组操作等高级功能。
3.1. 中间操作
3.1.1. filter(Predicate predicate)
filter 用于根据条件过滤流中的元素。它接受一个 Predicate 接口,该接口界说了一个条件,返回值为布尔类型。满意条件的元素会保存在流中,不满意的会被过滤掉。
示例:
- List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
- List<String> filteredNames = names.stream()
- .filter(name -> name.startsWith("A"))
- .collect(Collectors.toList());
- // 输出 ["Alice"]
复制代码 3.1.2. map(Function mapper)
map 用于将流中的每个元素转换为另一种形式。它接受一个 Function 接口,将元素逐一映射为新的值。
示例:
- List<String> words = Arrays.asList("apple", "banana", "cherry");
- List<Integer> wordLengths = words.stream()
- .map(String::length)
- .collect(Collectors.toList());
- // 输出 [5, 6, 6]
复制代码 3.1.3. flatMap(Function> mapper)
flatMap 用于将每个元素转换为一个流,然后将多个流合并为一个流。它适合处理惩罚嵌套的集合布局。
示例:
- List<List<String>> nestedList = Arrays.asList(
- Arrays.asList("one", "two"),
- Arrays.asList("three", "four")
- );
- List<String> flattenedList = nestedList.stream()
- .flatMap(List::stream)
- .collect(Collectors.toList());
- // 输出 ["one", "two", "three", "four"]
复制代码 3.1.4. distinct()
distinct 用于去除流中重复的元素,保存唯一值。
示例:
- List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 4, 4, 5);
- List<Integer> distinctNumbers = numbers.stream()
- .distinct()
- .collect(Collectors.toList());
- // 输出 [1, 2, 3, 4, 5]
复制代码 3.1.5. sorted() 和 sorted(Comparator comparator)
sorted 用于对流中的元素举行排序,默认是自然顺序。通过传入 Comparator 可以举行自界说排序。
示例:
- List<String> names = Arrays.asList("John", "Jane", "Mark", "Emily");
- List<String> sortedNames = names.stream()
- .sorted()
- .collect(Collectors.toList());
- // 输出 ["Emily", "Jane", "John", "Mark"]
- List<String> reverseSortedNames = names.stream()
- .sorted(Comparator.reverseOrder())
- .collect(Collectors.toList());
- // 输出 ["Mark", "John", "Jane", "Emily"]
复制代码 3.1.6. limit(long maxSize) 和 skip(long n)
- limit 截取流中前 maxSize 个元素。
- skip 跳过流中前 n 个元素。
示例:
- List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
- List<Integer> limitedNumbers = numbers.stream()
- .limit(3)
- .collect(Collectors.toList());
- // 输出 [1, 2, 3]
- List<Integer> skippedNumbers = numbers.stream()
- .skip(2)
- .collect(Collectors.toList());
- // 输出 [3, 4, 5, 6]
复制代码 3.2. 停止操作
3.2.1. collect(Collector collector)
collect 是用于将流中的元素收集到集合或其他数据布局中。常见的 Collector 工具包括 Collectors.toList()、Collectors.toSet()、Collectors.joining() 等。
示例:
- List<String> words = Arrays.asList("apple", "banana", "cherry");
- String joinedWords = words.stream()
- .collect(Collectors.joining(", "));
- // 输出 "apple, banana, cherry"
复制代码 3.2.2. forEach(Consumer action)
forEach 遍历流中的每个元素并执行给定的操作。
示例:
- List<String> items = Arrays.asList("item1", "item2", "item3");
- items.stream()
- .forEach(System.out::println);
- // 输出每个元素
复制代码 3.2.3. count()
count 返回流中元素的数量。
示例:
- List<String> names = Arrays.asList("John", "Jane", "Mark");
- long count = names.stream().count();
- // 输出 3
复制代码 3.2.4. reduce(BinaryOperator accumulator)
reduce 用于将流中的元素逐一组合成一个结果。
示例:
- List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
- Optional<Integer> sum = numbers.stream()
- .reduce((a, b) -> a + b);
- sum.ifPresent(System.out::println); // 输出 15
复制代码 3.2.5. findFirst() 和 findAny()
- findFirst 返回流中第一个元素的 Optional。
- findAny 返回流中任意一个元素的 Optional,通常用于并行流。
示例:
- List<String> items = Arrays.asList("apple", "banana", "cherry");
- Optional<String> firstItem = items.stream().findFirst();
- firstItem.ifPresent(System.out::println); // 输出 "apple"
复制代码 3.2.6. allMatch(Predicate predicate)、anyMatch(Predicate predicate)、noneMatch(Predicate predicate)
- allMatch 检查是否所有元素都满意给定条件。
- anyMatch 检查是否有任一元素满意给定条件。
- noneMatch 检查是否没有元素满意给定条件。
示例:
- List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
- boolean allEven = numbers.stream().allMatch(n -> n % 2 == 0); // false
- boolean anyEven = numbers.stream().anyMatch(n -> n % 2 == 0); // true
- boolean noneNegative = numbers.stream().noneMatch(n -> n < 0); // true
复制代码 3.3. 分组操作
Collectors.groupingBy() 是 Stream API 中的一个高级操作,它允许根据某个属性对元素举行分组。返回的结果是一个 Map,其中键是分组的依据,值是分组后的列表。
示例:按字符串长度分组
- List<String> words = Arrays.asList("apple", "banana", "cherry", "date");
- Map<Integer, List<String>> groupedByLength = words.stream()
- .collect(Collectors.groupingBy(String::length));
- groupedByLength.forEach((length, wordList) ->
- System.out.println("长度为 " + length + " 的单词: " + wordList)
- );
- // 输出:
- // 长度为 5 的单词: [apple]
- // 长度为 6 的单词: [banana, cherry]
- // 长度为 4 的单词: [date]
复制代码 示例:按员工部门分组
- class Employee {
- private String name;
- private String department;
- public Employee(String name, String department) {
- this.name = name;
- this.department = department;
- }
- public String getDepartment() {
- return department;
- }
- public String getName() {
- return name;
- }
- }
- List<Employee> employees = Arrays.asList(
- new Employee("Alice", "IT"),
- new Employee("Bob", "HR"),
- new Employee("Charlie", "IT"),
- new Employee("David", "Finance")
- );
- Map<String, List<Employee>> employeesByDepartment = employees.stream()
- .collect(Collectors.groupingBy(Employee::getDepartment));
- employeesByDepartment.forEach((department, empList) -> {
- System.out.println("部门 " + department + ": " +
- empList.stream().map(Employee::getName).collect(Collectors.joining(", ")));
- });
- // 输出:
- // 部门 IT: Alice, Charlie
- // 部门 HR: Bob
- // 部门 Finance: David
复制代码 多级分组
Collectors.groupingBy() 支持多级分组,这在实际项目中非常有用。例如,按部门和职位举行分组:
- Map<String, Map<String, List<Employee>>> multiLevelGrouping = employees.stream()
- .collect(Collectors.groupingBy(Employee::getDepartment,
- Collectors.groupingBy(Employee::getPosition)));
- // 返回嵌套 Map 结构,按部门和职位分组
复制代码 通过这些详尽的用法和分组操作,开发者可以更轻松地处理惩罚复杂数据操作,编写清晰、高效、可维护的代码。
4. Stream API 高级操作
4.1. 并行流
Java 8 提供了并行流(parallelStream()),可以充分利用多核 CPU,提拔数据处理惩罚的性能。并行流将流中的任务分发到多个线程中并行处理惩罚。
- List<Integer> largeList = IntStream.rangeClosed(1, 1000000)
- .boxed()
- .collect(Collectors.toList());
- long start = System.currentTimeMillis();
- largeList.parallelStream()
- .filter(n -> n % 2 == 0)
- .count();
- long end = System.currentTimeMillis();
- System.out.println("并行流处理时间: " + (end - start) + " ms");
复制代码 4.2. 惰性求值
Stream 的中间操作是惰性的,意味着它们在没有停止操作的情况下不会被执行。惰性求值有助于优化性能,淘汰不必要的计算。
- Stream<String> lazyStream = Stream.of("one", "two", "three", "four")
- .filter(s -> {
- System.out.println("正在处理: " + s);
- return s.length() > 3;
- });
- // 没有调用终止操作,filter 不会执行
- System.out.println("未触发终止操作");
- // 调用终止操作后,filter 才会被执行
- lazyStream.forEach(System.out::println);
复制代码 5. 常见题目及解决方式
在使用 Java 8 Stream API 时,开发者大概会遇到一些常见的坑和题目。理解这些题目及其解决方案有助于编写结实的代码。以下是几个常见题目及其应对方法。
5.1. Collectors.toMap() 时 Key 重复题目
在使用 Collectors.toMap() 将流转换为 Map 时,假如流中的键重复,就会抛出 IllegalStateException,提示键重复。为了避免此题目,我们可以通过提供合并函数来处理惩罚重复的键。
题目示例:
- List<String> items = Arrays.asList("apple", "banana", "apple", "orange");
- Map<String, Integer> itemMap = items.stream()
- .collect(Collectors.toMap(
- item -> item,
- item -> 1
- ));
- // 这段代码会抛出 IllegalStateException,因为 "apple" 键重复
复制代码 解决方案:使用合并函数
通过提供一个合并函数来解决键重复题目,例如选择保存第一个值或累加值。
- Map<String, Integer> itemMap = items.stream()
- .collect(Collectors.toMap(
- item -> item,
- item -> 1,
- (existingValue, newValue) -> existingValue + newValue // 合并函数
- ));
- // 输出:{apple=2, banana=1, orange=1}
复制代码 **解释:**合并函数 (existingValue, newValue) -> existingValue + newValue 表现当键重复时,将值举行累加。
5.2. NullPointerException 题目
在使用 Stream API 举行操作时,假如流中存在 null 值,操作如 map()、filter() 等大概会抛出 NullPointerException。为了避免这种情况,通常必要在操作前举行空值检查。
题目示例:
- List<String> words = Arrays.asList("apple", null, "banana", "cherry");
- List<Integer> wordLengths = words.stream()
- .map(String::length) // 如果遇到 null,会抛出 NullPointerException
- .collect(Collectors.toList());
复制代码 解决方案:使用 filter() 过滤 null 值
- List<Integer> wordLengths = words.stream()
- .filter(Objects::nonNull) // 过滤掉 null 值
- .map(String::length)
- .collect(Collectors.toList());
- // 输出 [5, 6, 6]
复制代码 5.3. ConcurrentModificationException 题目
当使用 Stream API 遍历集合并在迭代时修改集适时,会抛出 ConcurrentModificationException。这通常发生在对原始集合举行迭代并修改它的情况下。
题目示例:
- List<String> names = new ArrayList<>(Arrays.asList("John", "Jane", "Mark", "Emily"));
- names.stream().forEach(name -> {
- if (name.equals("Mark")) {
- names.remove(name); // 会抛出 ConcurrentModificationException
- }
- });
复制代码 解决方案:使用 removeIf() 方法或创建新的集合
- // 使用 removeIf()
- names.removeIf(name -> name.equals("Mark")); // 安全地删除元素
- // 或者,使用流创建新的集合
- List<String> filteredNames = names.stream()
- .filter(name -> !name.equals("Mark"))
- .collect(Collectors.toList());
复制代码 5.4. Stream 性能题目
虽然 Stream API 提供了优雅的语法,但在某些情况下会有性能题目,尤其是在使用 parallelStream() 时。并行流可以进步处理惩罚大量数据的性能,但在小数据集或 I/O 密集型操作中,大概会带来开销并导致性能降落。
建议:
- 在使用并行流前,分析数据规模和应用场景,确定是否有必要。
- 避免在 Stream 中举行复杂的同步操作或共享可变状态,以避免线程安全题目。
示例:
- List<Integer> numbers = IntStream.rangeClosed(1, 1000000)
- .boxed()
- .collect(Collectors.toList());
- // 并行流
- long start = System.currentTimeMillis();
- numbers.parallelStream()
- .filter(n -> n % 2 == 0)
- .count();
- long end = System.currentTimeMillis();
- System.out.println("并行流处理时间: " + (end - start) + " ms");
- // 顺序流
- start = System.currentTimeMillis();
- numbers.stream()
- .filter(n -> n % 2 == 0)
- .count();
- end = System.currentTimeMillis();
- System.out.println("顺序流处理时间: " + (end - start) + " ms");
复制代码 **注意:**在小数据集上,并行流的性能大概不如顺序流好。
5.5. 流的短路操作
Stream API 中有一些短路操作,可以淘汰不必要的处理惩罚,从而进步性能。
- findFirst() 和 findAny():返回第一个或任意一个符合条件的元素,适用于必要快速找到结果的情况。
- limit():截断流,适用于只必要处理惩罚部分数据时。
- anyMatch()、allMatch()、noneMatch():检查流中是否有满意条件的元素,支持短路操作。
示例:使用短路操作优化性能
- List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
- boolean hasEvenNumber = numbers.stream()
- .anyMatch(n -> n % 2 == 0); // 一旦找到第一个偶数,就会终止遍历
- System.out.println("是否存在偶数: " + hasEvenNumber); // 输出 true
复制代码 5.6. 数据并行性与共享可变状态
在使用并行流时,假如流中的元素共享了可变状态,大概会导致数据不一致或线程安全题目。
题目示例:
- List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
- List<Integer> results = new ArrayList<>();
- numbers.parallelStream()
- .forEach(results::add); // 可能会导致数据不一致
- System.out.println(results);
复制代码 解决方案:
使用线程安全的集合,如 Collections.synchronizedList() 或 ConcurrentLinkedQueue,或者使用 collect() 收集结果。
- List<Integer> results = numbers.parallelStream()
- .collect(Collectors.toList()); // 推荐做法
复制代码 总结起来,Stream API 提供了强大的功能和简洁的语法,但在实际项目中使用时,必要了解和避免常见的陷阱和题目,以确保代码的安全性和性能。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |