跳转至

Java Streams教程:深入理解与高效实践

简介

Java Streams 是 Java 8 引入的一个强大功能,它为处理集合数据提供了一种更高效、更简洁且函数式的编程方式。通过 Streams,开发者可以以声明式的风格处理数据集合,而不必编写繁琐的循环和条件语句。这篇博客将详细介绍 Java Streams 的基础概念、使用方法、常见实践以及最佳实践,帮助你全面掌握这一特性并在实际项目中高效运用。

目录

  1. 基础概念
    • 什么是 Stream
    • Stream 与 Collection 的区别
    • Stream 的操作类型
  2. 使用方法
    • 创建 Stream
    • 中间操作
    • 终端操作
  3. 常见实践
    • 过滤数据
    • 映射数据
    • 排序数据
    • 查找与匹配
    • 归约操作
  4. 最佳实践
    • 避免不必要的装箱和拆箱
    • 合理使用并行流
    • 保持流操作的简洁性
  5. 小结

基础概念

什么是 Stream

Stream 是 Java 中的一个接口,它代表了一系列支持顺序和并行聚合操作的元素序列。Stream 不是数据结构,它不会存储元素,而是提供了一种对数据进行处理的方式。Stream 操作通常会产生一个结果,这个结果不会修改原始数据,而是返回一个新的结果。

Stream 与 Collection 的区别

  • 数据存储:Collection 是用来存储数据的容器,而 Stream 并不存储数据,它只是对数据进行处理。
  • 处理方式:Collection 的遍历通常需要使用显式的循环(如 for 循环或增强型 for 循环),而 Stream 采用声明式的方式进行处理,更注重结果而不是过程。
  • 性能:Stream 支持并行处理,在处理大数据集时可以充分利用多核 CPU 的优势,提高处理效率。

Stream 的操作类型

Stream 的操作可以分为中间操作和终端操作: - 中间操作:返回一个新的 Stream,允许链式调用多个中间操作。中间操作是惰性执行的,只有当终端操作执行时,中间操作才会真正执行。 - 终端操作:执行 Stream 操作并返回一个结果,终端操作执行后,Stream 就会被消耗,不能再被使用。

使用方法

创建 Stream

  1. 从集合创建 ```java import java.util.Arrays; import java.util.List; import java.util.stream.Stream;

    public class StreamExample { public static void main(String[] args) { List numbers = Arrays.asList(1, 2, 3, 4, 5); Stream streamFromList = numbers.stream(); Stream parallelStreamFromList = numbers.parallelStream(); } } 2. **从数组创建**java import java.util.Arrays; import java.util.stream.Stream;

    public class StreamExample { public static void main(String[] args) { int[] numbersArray = {1, 2, 3, 4, 5}; Stream streamFromArray = Stream.of(numbersArray); // 对于基本类型数组,也可以使用 IntStream IntStream intStream = Arrays.stream(numbersArray); } } 3. **创建空 Stream**java import java.util.stream.Stream;

    public class StreamExample { public static void main(String[] args) { Stream emptyStream = Stream.empty(); } } ```

    中间操作

    1. 过滤(filter):根据条件过滤 Stream 中的元素。 ```java import java.util.Arrays; import java.util.List; import java.util.stream.Collectors;

      public class StreamExample { public static void main(String[] args) { List numbers = Arrays.asList(1, 2, 3, 4, 5); List evenNumbers = numbers.stream() .filter(n -> n % 2 == 0) .collect(Collectors.toList()); System.out.println(evenNumbers); // 输出: [2, 4] } } 2. **映射(map)**:将 Stream 中的每个元素映射为一个新的元素。java import java.util.Arrays; import java.util.List; import java.util.stream.Collectors;

      public class StreamExample { public static void main(String[] args) { List numbers = Arrays.asList(1, 2, 3, 4, 5); List squaredNumbers = numbers.stream() .map(n -> n * n) .collect(Collectors.toList()); System.out.println(squaredNumbers); // 输出: [1, 4, 9, 16, 25] } } 3. **排序(sorted)**:对 Stream 中的元素进行排序。java import java.util.Arrays; import java.util.List; import java.util.stream.Collectors;

      public class StreamExample { public static void main(String[] args) { List numbers = Arrays.asList(5, 2, 4, 1, 3); List sortedNumbers = numbers.stream() .sorted() .collect(Collectors.toList()); System.out.println(sortedNumbers); // 输出: [1, 2, 3, 4, 5] } } ```

    终端操作

    1. 收集(collect):将 Stream 中的元素收集到一个集合中。 ```java import java.util.Arrays; import java.util.List; import java.util.stream.Collectors;

      public class StreamExample { public static void main(String[] args) { List numbers = Arrays.asList(1, 2, 3, 4, 5); List evenNumbers = numbers.stream() .filter(n -> n % 2 == 0) .collect(Collectors.toList()); System.out.println(evenNumbers); // 输出: [2, 4] } } 2. **计数(count)**:返回 Stream 中元素的个数。java import java.util.Arrays; import java.util.List;

      public class StreamExample { public static void main(String[] args) { List numbers = Arrays.asList(1, 2, 3, 4, 5); long count = numbers.stream() .filter(n -> n % 2 == 0) .count(); System.out.println(count); // 输出: 2 } } 3. **forEach**:对 Stream 中的每个元素执行一个操作。java import java.util.Arrays; import java.util.List;

      public class StreamExample { public static void main(String[] args) { List numbers = Arrays.asList(1, 2, 3, 4, 5); numbers.stream() .forEach(System.out::println); } } ```

    常见实践

    过滤数据

    在处理数据时,经常需要根据某些条件过滤出符合要求的元素。例如,从一个学生列表中过滤出成绩大于 80 分的学生:

    import java.util.ArrayList;
    import java.util.List;
    
    class Student {
        private String name;
        private int score;
    
        public Student(String name, int score) {
            this.name = name;
            this.score = score;
        }
    
        public int getScore() {
            return score;
        }
    }
    
    public class StreamFilterExample {
        public static void main(String[] args) {
            List<Student> students = new ArrayList<>();
            students.add(new Student("Alice", 85));
            students.add(new Student("Bob", 70));
            students.add(new Student("Charlie", 90));
    
            List<Student> highScorers = students.stream()
              .filter(student -> student.getScore() > 80)
              .collect(Collectors.toList());
    
            highScorers.forEach(student -> System.out.println(student.getName() + ": " + student.getScore()));
        }
    }
    

    映射数据

    映射操作可以将一种类型的数据转换为另一种类型。比如,将一个字符串列表中的每个字符串转换为其长度:

    import java.util.Arrays;
    import java.util.List;
    import java.util.stream.Collectors;
    
    public class StreamMapExample {
        public static void main(String[] args) {
            List<String> words = Arrays.asList("apple", "banana", "cherry");
            List<Integer> wordLengths = words.stream()
              .map(String::length)
              .collect(Collectors.toList());
            System.out.println(wordLengths); // 输出: [5, 6, 6]
        }
    }
    

    排序数据

    对数据进行排序是常见的需求。假设我们有一个包含员工年龄的列表,要对其进行升序排序:

    import java.util.Arrays;
    import java.util.List;
    import java.util.stream.Collectors;
    
    public class StreamSortExample {
        public static void main(String[] args) {
            List<Integer> ages = Arrays.asList(30, 25, 35, 20);
            List<Integer> sortedAges = ages.stream()
              .sorted()
              .collect(Collectors.toList());
            System.out.println(sortedAges); // 输出: [20, 25, 30, 35]
        }
    }
    

    查找与匹配

    可以使用 Stream 进行查找和匹配操作。例如,判断一个列表中是否存在某个元素:

    import java.util.Arrays;
    import java.util.List;
    
    public class StreamFindAndMatchExample {
        public static void main(String[] args) {
            List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
            boolean exists = numbers.stream()
              .anyMatch(n -> n == 3);
            System.out.println(exists); // 输出: true
        }
    }
    

    归约操作

    归约操作可以将 Stream 中的元素组合成一个值。比如,计算一个整数列表的总和:

    import java.util.Arrays;
    import java.util.List;
    import java.util.Optional;
    
    public class StreamReduceExample {
        public static void main(String[] args) {
            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
        }
    }
    

    最佳实践

    避免不必要的装箱和拆箱

    在使用 Stream 处理基本类型数据时,尽量使用对应的原始类型 Stream,如 IntStream、DoubleStream 等,避免自动装箱和拆箱带来的性能开销。

    import java.util.Arrays;
    import java.util.IntSummaryStatistics;
    import java.util.stream.IntStream;
    
    public class PrimitiveStreamExample {
        public static void main(String[] args) {
            int[] numbers = {1, 2, 3, 4, 5};
            IntSummaryStatistics stats = IntStream.of(numbers)
              .summaryStatistics();
            System.out.println("Sum: " + stats.getSum());
            System.out.println("Average: " + stats.getAverage());
        }
    }
    

    合理使用并行流

    并行流可以利用多核 CPU 提高处理效率,但并不是在所有情况下都适用。在使用并行流时,要考虑数据量大小、操作的复杂度以及数据的依赖性。对于小数据集或操作复杂度较低的情况,并行流可能会带来额外的开销,反而降低性能。

    import java.util.Arrays;
    import java.util.List;
    import java.util.stream.Collectors;
    
    public class ParallelStreamExample {
        public static void main(String[] args) {
            List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
            List<Integer> squaredNumbers = numbers.parallelStream()
              .map(n -> n * n)
              .collect(Collectors.toList());
            System.out.println(squaredNumbers);
        }
    }
    

    保持流操作的简洁性

    尽量保持 Stream 操作的简洁和可读性,避免在 Stream 操作中编写复杂的逻辑。如果逻辑过于复杂,可以将其提取到单独的方法中,然后在 Stream 操作中调用这些方法。

    import java.util.Arrays;
    import java.util.List;
    import java.util.stream.Collectors;
    
    public class CleanStreamExample {
        public static boolean isEven(int number) {
            return number % 2 == 0;
        }
    
        public static int square(int number) {
            return number * number;
        }
    
        public static void main(String[] args) {
            List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
            List<Integer> squaredEvenNumbers = numbers.stream()
              .filter(CleanStreamExample::isEven)
              .map(CleanStreamExample::square)
              .collect(Collectors.toList());
            System.out.println(squaredEvenNumbers);
        }
    }
    

    小结

    通过这篇博客,我们详细介绍了 Java Streams 的基础概念、使用方法、常见实践以及最佳实践。Java Streams 为处理集合数据提供了一种简洁、高效且函数式的编程方式,能够大大提高代码的可读性和性能。在实际开发中,合理运用 Stream 可以使代码更加优雅和高效。希望读者通过学习和实践,能够熟练掌握 Java Streams 并在项目中发挥其优势。

    希望这篇博客对你理解和使用 Java Streams 有所帮助。如果你有任何问题或建议,欢迎在评论区留言。