Java 8 Stream用法与常见题目息争决方式

打印 上一主题 下一主题

主题 882|帖子 882|积分 2646

1. 什么是 Stream API?

Stream API 是用于处理惩罚数据序列的功能,提供了一种高效、清晰和声明性的处理惩罚方式。Stream 不会存储数据,它是对数据源的高级抽象,可以举行聚合操作(如过滤、映射、排序、归约等)。Stream 的核心优势包括:


  • 支持链式调用,增强代码可读性。
  • 惰性求值特性,避免不必要的计算。
  • 并行处理惩罚能力,充分利用多核 CPU 提拔性能。
2. Stream API 底子

Stream 可以通过多种方式创建,常见的有以下几种:


  • 从 Collection 或 List 中获取。
  • 使用 Stream.of() 方法。
  • 使用 Arrays.stream() 处理惩罚数组。
  • 使用文件或 I/O 操作天生。
示例:

  1. List<String> stringList = Arrays.asList("Java", "Python", "C++", "JavaScript");
  2. // 从集合获取 Stream
  3. Stream<String> streamFromList = stringList.stream();
  4. // 使用 Stream.of()
  5. Stream<String> streamOfStrings = Stream.of("Hello", "World");
  6. // 使用 Arrays.stream()
  7. int[] intArray = {1, 2, 3, 4, 5};
  8. IntStream intStream = Arrays.stream(intArray);
复制代码
3. Stream API 常见操作详解

Stream API 提供了丰富的操作来处理惩罚数据序列,这些操作分为中间操作和停止操作。中间操作不会立即执行,它们是惰性的,只有在停止操作触发时才会执行。停止操作会结束流的处理惩罚并天生结果。以下是对 Stream API 常见操作的细化讲解,包括分组操作等高级功能。
3.1. 中间操作

3.1.1. filter(Predicate predicate)

filter 用于根据条件过滤流中的元素。它接受一个 Predicate 接口,该接口界说了一个条件,返回值为布尔类型。满意条件的元素会保存在流中,不满意的会被过滤掉。
示例:
  1. List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
  2. List<String> filteredNames = names.stream()
  3.                                   .filter(name -> name.startsWith("A"))
  4.                                   .collect(Collectors.toList());
  5. // 输出 ["Alice"]
复制代码
3.1.2. map(Function mapper)

map 用于将流中的每个元素转换为另一种形式。它接受一个 Function 接口,将元素逐一映射为新的值。
示例:
  1. List<String> words = Arrays.asList("apple", "banana", "cherry");
  2. List<Integer> wordLengths = words.stream()
  3.                                  .map(String::length)
  4.                                  .collect(Collectors.toList());
  5. // 输出 [5, 6, 6]
复制代码
3.1.3. flatMap(Function> mapper)

flatMap 用于将每个元素转换为一个流,然后将多个流合并为一个流。它适合处理惩罚嵌套的集合布局。
示例:
  1. List<List<String>> nestedList = Arrays.asList(
  2.     Arrays.asList("one", "two"),
  3.     Arrays.asList("three", "four")
  4. );
  5. List<String> flattenedList = nestedList.stream()
  6.                                        .flatMap(List::stream)
  7.                                        .collect(Collectors.toList());
  8. // 输出 ["one", "two", "three", "four"]
复制代码
3.1.4. distinct()

distinct 用于去除流中重复的元素,保存唯一值。
示例:
  1. List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 4, 4, 5);
  2. List<Integer> distinctNumbers = numbers.stream()
  3.                                        .distinct()
  4.                                        .collect(Collectors.toList());
  5. // 输出 [1, 2, 3, 4, 5]
复制代码
3.1.5. sorted() 和 sorted(Comparator comparator)

sorted 用于对流中的元素举行排序,默认是自然顺序。通过传入 Comparator 可以举行自界说排序。
示例:
  1. List<String> names = Arrays.asList("John", "Jane", "Mark", "Emily");
  2. List<String> sortedNames = names.stream()
  3.                                 .sorted()
  4.                                 .collect(Collectors.toList());
  5. // 输出 ["Emily", "Jane", "John", "Mark"]
  6. List<String> reverseSortedNames = names.stream()
  7.                                        .sorted(Comparator.reverseOrder())
  8.                                        .collect(Collectors.toList());
  9. // 输出 ["Mark", "John", "Jane", "Emily"]
复制代码
3.1.6. limit(long maxSize) 和 skip(long n)



  • limit 截取流中前 maxSize 个元素。
  • skip 跳过流中前 n 个元素。
示例:
  1. List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
  2. List<Integer> limitedNumbers = numbers.stream()
  3.                                       .limit(3)
  4.                                       .collect(Collectors.toList());
  5. // 输出 [1, 2, 3]
  6. List<Integer> skippedNumbers = numbers.stream()
  7.                                       .skip(2)
  8.                                       .collect(Collectors.toList());
  9. // 输出 [3, 4, 5, 6]
复制代码
3.2. 停止操作

3.2.1. collect(Collector collector)

collect 是用于将流中的元素收集到集合或其他数据布局中。常见的 Collector 工具包括 Collectors.toList()、Collectors.toSet()、Collectors.joining() 等。
示例:
  1. List<String> words = Arrays.asList("apple", "banana", "cherry");
  2. String joinedWords = words.stream()
  3.                           .collect(Collectors.joining(", "));
  4. // 输出 "apple, banana, cherry"
复制代码
3.2.2. forEach(Consumer action)

forEach 遍历流中的每个元素并执行给定的操作。
示例:
  1. List<String> items = Arrays.asList("item1", "item2", "item3");
  2. items.stream()
  3.      .forEach(System.out::println);
  4. // 输出每个元素
复制代码
3.2.3. count()

count 返回流中元素的数量。
示例:
  1. List<String> names = Arrays.asList("John", "Jane", "Mark");
  2. long count = names.stream().count();
  3. // 输出 3
复制代码
3.2.4. reduce(BinaryOperator accumulator)

reduce 用于将流中的元素逐一组合成一个结果。
示例:
  1. List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
  2. Optional<Integer> sum = numbers.stream()
  3.                                .reduce((a, b) -> a + b);
  4. sum.ifPresent(System.out::println);  // 输出 15
复制代码
3.2.5. findFirst() 和 findAny()



  • findFirst 返回流中第一个元素的 Optional。
  • findAny 返回流中任意一个元素的 Optional,通常用于并行流。
示例:
  1. List<String> items = Arrays.asList("apple", "banana", "cherry");
  2. Optional<String> firstItem = items.stream().findFirst();
  3. firstItem.ifPresent(System.out::println);  // 输出 "apple"
复制代码
3.2.6. allMatch(Predicate predicate)、anyMatch(Predicate predicate)、noneMatch(Predicate predicate)



  • allMatch 检查是否所有元素都满意给定条件。
  • anyMatch 检查是否有任一元素满意给定条件。
  • noneMatch 检查是否没有元素满意给定条件。
示例:
  1. List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
  2. boolean allEven = numbers.stream().allMatch(n -> n % 2 == 0); // false
  3. boolean anyEven = numbers.stream().anyMatch(n -> n % 2 == 0); // true
  4. boolean noneNegative = numbers.stream().noneMatch(n -> n < 0); // true
复制代码
3.3. 分组操作

Collectors.groupingBy() 是 Stream API 中的一个高级操作,它允许根据某个属性对元素举行分组。返回的结果是一个 Map,其中键是分组的依据,值是分组后的列表。
示例:按字符串长度分组
  1. List<String> words = Arrays.asList("apple", "banana", "cherry", "date");
  2. Map<Integer, List<String>> groupedByLength = words.stream()
  3.                                                   .collect(Collectors.groupingBy(String::length));
  4. groupedByLength.forEach((length, wordList) ->
  5.     System.out.println("长度为 " + length + " 的单词: " + wordList)
  6. );
  7. // 输出:
  8. // 长度为 5 的单词: [apple]
  9. // 长度为 6 的单词: [banana, cherry]
  10. // 长度为 4 的单词: [date]
复制代码
示例:按员工部门分组
  1. class Employee {
  2.     private String name;
  3.     private String department;
  4.     public Employee(String name, String department) {
  5.         this.name = name;
  6.         this.department = department;
  7.     }
  8.     public String getDepartment() {
  9.         return department;
  10.     }
  11.     public String getName() {
  12.         return name;
  13.     }
  14. }
  15. List<Employee> employees = Arrays.asList(
  16.     new Employee("Alice", "IT"),
  17.     new Employee("Bob", "HR"),
  18.     new Employee("Charlie", "IT"),
  19.     new Employee("David", "Finance")
  20. );
  21. Map<String, List<Employee>> employeesByDepartment = employees.stream()
  22.                                                              .collect(Collectors.groupingBy(Employee::getDepartment));
  23. employeesByDepartment.forEach((department, empList) -> {
  24.     System.out.println("部门 " + department + ": " +
  25.         empList.stream().map(Employee::getName).collect(Collectors.joining(", ")));
  26. });
  27. // 输出:
  28. // 部门 IT: Alice, Charlie
  29. // 部门 HR: Bob
  30. // 部门 Finance: David
复制代码
多级分组

Collectors.groupingBy() 支持多级分组,这在实际项目中非常有用。例如,按部门和职位举行分组:
  1. Map<String, Map<String, List<Employee>>> multiLevelGrouping = employees.stream()
  2.     .collect(Collectors.groupingBy(Employee::getDepartment,
  3.               Collectors.groupingBy(Employee::getPosition)));
  4. // 返回嵌套 Map 结构,按部门和职位分组
复制代码
通过这些详尽的用法和分组操作,开发者可以更轻松地处理惩罚复杂数据操作,编写清晰、高效、可维护的代码。
4. Stream API 高级操作

4.1. 并行流

Java 8 提供了并行流(parallelStream()),可以充分利用多核 CPU,提拔数据处理惩罚的性能。并行流将流中的任务分发到多个线程中并行处理惩罚。
  1. List<Integer> largeList = IntStream.rangeClosed(1, 1000000)
  2.                                    .boxed()
  3.                                    .collect(Collectors.toList());
  4. long start = System.currentTimeMillis();
  5. largeList.parallelStream()
  6.          .filter(n -> n % 2 == 0)
  7.          .count();
  8. long end = System.currentTimeMillis();
  9. System.out.println("并行流处理时间: " + (end - start) + " ms");
复制代码
4.2. 惰性求值

Stream 的中间操作是惰性的,意味着它们在没有停止操作的情况下不会被执行。惰性求值有助于优化性能,淘汰不必要的计算。
  1. Stream<String> lazyStream = Stream.of("one", "two", "three", "four")
  2.                                   .filter(s -> {
  3.                                       System.out.println("正在处理: " + s);
  4.                                       return s.length() > 3;
  5.                                   });
  6. // 没有调用终止操作,filter 不会执行
  7. System.out.println("未触发终止操作");
  8. // 调用终止操作后,filter 才会被执行
  9. lazyStream.forEach(System.out::println);
复制代码
5. 常见题目及解决方式

在使用 Java 8 Stream API 时,开发者大概会遇到一些常见的坑和题目。理解这些题目及其解决方案有助于编写结实的代码。以下是几个常见题目及其应对方法。
5.1. Collectors.toMap() 时 Key 重复题目

在使用 Collectors.toMap() 将流转换为 Map 时,假如流中的键重复,就会抛出 IllegalStateException,提示键重复。为了避免此题目,我们可以通过提供合并函数来处理惩罚重复的键。
题目示例:
  1. List<String> items = Arrays.asList("apple", "banana", "apple", "orange");
  2. Map<String, Integer> itemMap = items.stream()
  3.                                     .collect(Collectors.toMap(
  4.                                         item -> item,
  5.                                         item -> 1
  6.                                     ));
  7. // 这段代码会抛出 IllegalStateException,因为 "apple" 键重复
复制代码
解决方案:使用合并函数
通过提供一个合并函数来解决键重复题目,例如选择保存第一个值或累加值。
  1. Map<String, Integer> itemMap = items.stream()
  2.                                     .collect(Collectors.toMap(
  3.                                         item -> item,
  4.                                         item -> 1,
  5.                                         (existingValue, newValue) -> existingValue + newValue // 合并函数
  6.                                     ));
  7. // 输出:{apple=2, banana=1, orange=1}
复制代码
**解释:**合并函数 (existingValue, newValue) -> existingValue + newValue 表现当键重复时,将值举行累加。
5.2. NullPointerException 题目

在使用 Stream API 举行操作时,假如流中存在 null 值,操作如 map()、filter() 等大概会抛出 NullPointerException。为了避免这种情况,通常必要在操作前举行空值检查。
题目示例:
  1. List<String> words = Arrays.asList("apple", null, "banana", "cherry");
  2. List<Integer> wordLengths = words.stream()
  3.                                  .map(String::length) // 如果遇到 null,会抛出 NullPointerException
  4.                                  .collect(Collectors.toList());
复制代码
解决方案:使用 filter() 过滤 null 值
  1. List<Integer> wordLengths = words.stream()
  2.                                  .filter(Objects::nonNull) // 过滤掉 null 值
  3.                                  .map(String::length)
  4.                                  .collect(Collectors.toList());
  5. // 输出 [5, 6, 6]
复制代码
5.3. ConcurrentModificationException 题目

当使用 Stream API 遍历集合并在迭代时修改集适时,会抛出 ConcurrentModificationException。这通常发生在对原始集合举行迭代并修改它的情况下。
题目示例:
  1. List<String> names = new ArrayList<>(Arrays.asList("John", "Jane", "Mark", "Emily"));
  2. names.stream().forEach(name -> {
  3.     if (name.equals("Mark")) {
  4.         names.remove(name); // 会抛出 ConcurrentModificationException
  5.     }
  6. });
复制代码
解决方案:使用 removeIf() 方法或创建新的集合
  1. // 使用 removeIf()
  2. names.removeIf(name -> name.equals("Mark")); // 安全地删除元素
  3. // 或者,使用流创建新的集合
  4. List<String> filteredNames = names.stream()
  5.                                   .filter(name -> !name.equals("Mark"))
  6.                                   .collect(Collectors.toList());
复制代码
5.4. Stream 性能题目

虽然 Stream API 提供了优雅的语法,但在某些情况下会有性能题目,尤其是在使用 parallelStream() 时。并行流可以进步处理惩罚大量数据的性能,但在小数据集或 I/O 密集型操作中,大概会带来开销并导致性能降落。
建议:


  • 在使用并行流前,分析数据规模和应用场景,确定是否有必要。
  • 避免在 Stream 中举行复杂的同步操作或共享可变状态,以避免线程安全题目。
示例:
  1. List<Integer> numbers = IntStream.rangeClosed(1, 1000000)
  2.                                  .boxed()
  3.                                  .collect(Collectors.toList());
  4. // 并行流
  5. long start = System.currentTimeMillis();
  6. numbers.parallelStream()
  7.        .filter(n -> n % 2 == 0)
  8.        .count();
  9. long end = System.currentTimeMillis();
  10. System.out.println("并行流处理时间: " + (end - start) + " ms");
  11. // 顺序流
  12. start = System.currentTimeMillis();
  13. numbers.stream()
  14.        .filter(n -> n % 2 == 0)
  15.        .count();
  16. end = System.currentTimeMillis();
  17. System.out.println("顺序流处理时间: " + (end - start) + " ms");
复制代码
**注意:**在小数据集上,并行流的性能大概不如顺序流好。
5.5. 流的短路操作

Stream API 中有一些短路操作,可以淘汰不必要的处理惩罚,从而进步性能。


  • findFirst() 和 findAny():返回第一个或任意一个符合条件的元素,适用于必要快速找到结果的情况。
  • limit():截断流,适用于只必要处理惩罚部分数据时。
  • anyMatch()、allMatch()、noneMatch():检查流中是否有满意条件的元素,支持短路操作。
示例:使用短路操作优化性能
  1. List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
  2. boolean hasEvenNumber = numbers.stream()
  3.                                .anyMatch(n -> n % 2 == 0); // 一旦找到第一个偶数,就会终止遍历
  4. System.out.println("是否存在偶数: " + hasEvenNumber); // 输出 true
复制代码
5.6. 数据并行性与共享可变状态

在使用并行流时,假如流中的元素共享了可变状态,大概会导致数据不一致或线程安全题目。
题目示例:
  1. List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
  2. List<Integer> results = new ArrayList<>();
  3. numbers.parallelStream()
  4.        .forEach(results::add); // 可能会导致数据不一致
  5. System.out.println(results);
复制代码
解决方案:
使用线程安全的集合,如 Collections.synchronizedList() 或 ConcurrentLinkedQueue,或者使用 collect() 收集结果。
  1. List<Integer> results = numbers.parallelStream()
  2.                                .collect(Collectors.toList()); // 推荐做法
复制代码
总结起来,Stream API 提供了强大的功能和简洁的语法,但在实际项目中使用时,必要了解和避免常见的陷阱和题目,以确保代码的安全性和性能。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

南飓风

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表