Java Stream流

402 阅读9分钟

Java Stream是Java 8中引入的一个新特性,它为开发人员提供了一种简单、高效的方法来处理集合数据。Java Stream允许开发人员对数据进行并行处理,提高了代码的可读性和可维护性。

Java Stream是一个基于流式处理的API,它可以让开发人员以一种更函数式的方式来处理数据。使用Java Stream,可以避免繁琐的循环和条件语句,而是使用类似于SQL的语法来对数据进行操作。

Java Stream主要有三个组成部分:源、中间操作和终端操作。源是指用于提供数据的数据结构,例如集合或数组。中间操作是在源上执行的操作,例如过滤、映射和排序。终端操作是指最终将结果返回的操作,例如归约、收集和打印。

Java Stream的主要好处之一是它支持并行处理。这意味着,在处理大量数据时,Java Stream可以将数据分成多个块,然后在多个CPU上并行处理这些块。这可以显著提高处理数据的速度。

以下是一些Java Stream的例子,可以帮助您更好地理解它们的用法:

1. 中间操作

Java Stream中的中间操作可以分为以下几种类型:

1.1 过滤操作

过滤操作可以用于筛选数据流中满足特定条件的元素,它接受一个Predicate函数式接口作为参数,该接口用于测试元素是否符合条件。以下是一个使用过滤操作的例子:

List<String> names = Arrays.asList("John", "Peter", "Susan", "Mary", "Tom");
List<String> shortNames = names.stream()
                                .filter(name -> name.length() < 5)
                                .collect(Collectors.toList());
System.out.println(shortNames);

在上面的例子中,我们使用filter方法对names列表进行过滤操作,筛选出长度小于5的元素,并使用collect方法将结果收集到一个列表中。

1.2 映射操作

映射操作可以用于将数据流中的元素转换成另一种形式,它接受一个Function函数式接口作为参数,该接口用于对元素进行转换。以下是一个使用映射操作的例子:

List<String> words = Arrays.asList("Java", "Python", "Ruby", "JavaScript");
List<Integer> wordLengths = words.stream()
                                    .map(String::length)
                                    .collect(Collectors.toList());
System.out.println(wordLengths);

在上面的例子中,我们使用map方法将words列表中的每个字符串转换为它的长度,并将结果收集到一个列表中。

1.3 排序操作

排序操作可以用于对数据流中的元素进行排序,它接受一个Comparator函数式接口作为参数,该接口用于比较元素的大小。以下是一个使用排序操作的例子:

List<String> names = Arrays.asList("John", "Peter", "Susan", "Mary", "Tom");
List<String> sortedNames = names.stream()
                                    .sorted()
                                    .collect(Collectors.toList());
System.out.println(sortedNames);

在上面的例子中,我们使用sorted方法对names列表中的元素进行排序,并将结果收集到一个列表中。

1.4 去重操作

去重操作可以用于去除数据流中的重复元素,它使用元素的equals方法来判断元素是否相等。以下是一个使用去重操作的例子:

List<String> names = Arrays.asList("John", "Peter", "Susan", "Mary", "Tom", "John");
List<String> uniqueNames = names.stream()
                                    .distinct()
                                    .collect(Collectors.toList());
System.out.println(uniqueNames);

在上面的例子中,我们使用distinct方法对names列表去重。

2. 终端操作

Java Stream中的终端操作可以分为以下几种类型:

2.1 forEach

forEach操作可以用于对数据流中的每个元素执行指定的操作,它接受一个Consumer函数式接口作为参数,该接口用于对元素执行操作。以下是一个使用forEach操作的例子:

List<String> names = Arrays.asList("John", "Peter", "Susan", "Mary", "Tom");
names.stream()
        .forEach(name -> System.out.println(name));

在上面的例子中,我们使用forEach方法对names列表中的每个元素执行打印操作。

2.2 count

count操作可以用于统计数据流中元素的数量,它返回一个long类型的值。以下是一个使用count操作的例子:

List<String> names = Arrays.asList("John", "Peter", "Susan", "Mary", "Tom");
long count = names.stream()
                    .count();
System.out.println(count);

在上面的例子中,我们使用count方法统计names列表中元素的数量。

2.3 collect

collect操作可以用于将数据流中的元素收集到一个集合中,它接受一个Collector接口作为参数,该接口定义了如何将元素收集到集合中。以下是一个使用collect操作的例子:

List<String> names = Arrays.asList("John", "Peter", "Susan", "Mary", "Tom");
List<String> collectedNames = names.stream()
                                        .filter(name -> name.length() < 5)
                                        .collect(Collectors.toList());
System.out.println(collectedNames);

在上面的例子中,我们使用collect方法将数据流中长度小于5的元素收集到一个列表中。

2.4 reduce

reduce操作可以用于将数据流中的元素组合成一个结果,它接受一个BinaryOperator函数式接口作为参数,该接口定义了如何组合元素。以下是一个使用reduce操作的例子:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
                    .reduce(0, (a, b) -> a + b);
System.out.println(sum);

在上面的例子中,我们使用reduce方法将数据流中的元素相加得到一个结果。

3. 并行流

当使用并行流时,Java会将数据流分成多个部分,并分配给不同的线程进行处理。这样可以大大提高处理效率,尤其是当处理的数据量较大时。以下是一些使用并行流的例子:

3.1 数组求和

使用并行流可以快速地求出数组中元素的和。

int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

// 串行流求和
int sum = Arrays.stream(array).sum();
System.out.println(sum); // 输出55

// 并行流求和
int parallelSum = Arrays.stream(array).parallel().sum();
System.out.println(parallelSum); // 输出55

3.2 列表排序

使用并行流可以快速地对列表进行排序。

List<Integer> list = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5);

// 串行流排序
List<Integer> sortedList = list.stream().sorted().collect(Collectors.toList());
System.out.println(sortedList); // 输出[1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9]

// 并行流排序
List<Integer> parallelSortedList = list.parallelStream().sorted().collect(Collectors.toList());
System.out.println(parallelSortedList); // 输出[1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9]

3.3 数据去重

使用并行流可以快速地对数据进行去重操作。

List<Integer> list = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5);

// 串行流去重
List<Integer> distinctList = list.stream().distinct().collect(Collectors.toList());
System.out.println(distinctList); // 输出[3, 1, 4, 5, 9, 2, 6]

// 并行流去重
List<Integer> parallelDistinctList = list.parallelStream().distinct().collect(Collectors.toList());
System.out.println(parallelDistinctList); // 输出[3, 1, 4, 5, 9, 2, 6]

3.4 数据过滤

使用并行流可以快速地对数据进行过滤操作。

List<Integer> list = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5);

// 串行流过滤
List<Integer> filteredList = list.stream().filter(n -> n > 3).collect(Collectors.toList());
System.out.println(filteredList); // 输出[4, 5, 9, 6, 5, 5]

// 并行流过滤
List<Integer> parallelFilteredList = list.parallelStream().filter(n -> n > 3).collect(Collectors.toList());
System.out.println(parallelFilteredList); // 输出[4, 5, 9, 6, 5, 5]

3.5 多重过滤

使用并行流可以快速地对数据进行多重过滤操作。

List<Integer> list = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5);

// 串行流多重过滤
List<Integer> filteredList = list.stream()
        .filter(n -> n > 3)
        .filter(n -> n % 2 == 0)
        .collect(Collectors.toList());
System.out.println(filteredList); // 输出[4, 6]

// 并行流多重过滤
List<Integer> parallelFilteredList = list.parallelStream()
        .filter(n -> n > 3)
        .filter(n -> n % 2 == 0)
        .collect(Collectors.toList());
System.out.println(parallelFilteredList); // 输出[4, 6]

3.6 对象列表处理

使用并行流可以快速地对对象列表进行处理操作。

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

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

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + ''' +
                ", age=" + age +
                '}';
    }
}

List<Person> personList = Arrays.asList(
        new Person("Tom", 20),
        new Person("Jerry", 25),
        new Person("Mike", 30),
        new Person("John", 35),
        new Person("Lucy", 40)
);

// 串行流处理
List<String> nameList = personList.stream().map(Person::getName).collect(Collectors.toList());
System.out.println(nameList); // 输出[Tom, Jerry, Mike, John, Lucy]

// 并行流处理
List<String> parallelNameList = personList.parallelStream().map(Person::getName).collect(Collectors.toList());
System.out.println(parallelNameList); // 输出[Tom, Jerry, Mike, John, Lucy]

3.7 分组操作

使用并行流可以快速地对数据进行分组操作。

List<Person> personList = Arrays.asList(
        new Person("Tom", 20),
        new Person("Jerry", 25),
        new Person("Mike", 30),
        new Person("John", 35),
        new Person("Lucy", 40)
);

// 串行流分组
Map<Integer, List<Person>> ageGroupMap = personList.stream().collect(Collectors.groupingBy(Person::getAge));
System.out.println(ageGroupMap); // 输出{20=[Person{name='Tom', age=20}], 25=[Person{name='Jerry', age=25}], 30=[Person{name='Mike', age=30}], 35=[Person{name='John', age=35}], 40=[Person{name='Lucy', age=40}]}

// 并行流分组
Map<Integer, List<Person>> parallelAgeGroupMap = personList.parallelStream().collect(Collectors.groupingBy(Person::getAge));
System.out.println(parallelAgeGroupMap); // 输出{20=[Person{name='Tom', age=20}], 25=[Person{name='Jerry', age=25}], 30=[Person{name='Mike', age=30}], 35=[Person{name='John', age=35}], 40=[Person{name='Lucy', age=40}]}

3.8 统计操作

使用并行流可以快速地对数据进行统计操作。

List<Integer> list = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5);

// 串行流统计操作
IntSummaryStatistics summaryStatistics = list.stream().mapToInt(Integer::intValue).summaryStatistics();
System.out.println(summaryStatistics); // 输出IntSummaryStatistics{count=11, sum=39, min=1, average=3.545455, max=9}

// 并行流统计操作
IntSummaryStatistics parallelSummaryStatistics = list.parallelStream().mapToInt(Integer::intValue).summaryStatistics();
System.out.println(parallelSummaryStatistics); // 输出IntSummaryStatistics{count=11, sum=39, min=1, average=3.545455, max=9}

3.9 并行流的注意事项

使用并行流需要注意以下几点:

  • 数据量必须很大才能体现并行流的优势。如果数据量不大,反而可能因为线程间的竞争而导致性能下降。
  • 数据源是否易于分解。如果数据源是LinkedList等不能轻易分解的数据结构,那么并行流的效果可能不如串行流。
  • 是否需要保证结果的有序性。如果需要保证结果的有序性,那么使用并行流可能不是最优选择,因为排序是有序的操作,会导致数据在多个线程间传输和合并,效率会降低。
  • 是否需要避免产生副作用。如果操作会改变数据源,那么使用并行流可能会导致不确定的结果,需要注意。

4.性能优化

Java Stream是一种高效且易于使用的API,但是在处理大量数据时,可能会遇到性能问题。以下是一些性能优化的建议:

4.1 避免创建过多的中间流

在使用Java Stream时,我们通常会进行多次转换操作,从而形成多个中间流。如果创建过多的中间流,会导致内存消耗增加,从而影响性能。因此,在编写代码时,应该尽可能地减少中间流的创建,可以通过使用链式调用的方式将多个转换操作合并到一个流中,从而减少中间流的创建。

4.2 使用并行流

并行流可以将数据流分成多个部分进行并行处理,从而提高处理效率。但是,并行流的使用也需要谨慎,因为并行处理可能会导致线程安全问题和性能问题。在使用并行流时,应该考虑数据量、数据结构、硬件配置等因素,从而确定是否使用并行流。

4.3 使用基本类型流

Java Stream提供了基本类型流,例如IntStream、LongStream和DoubleStream,它们可以避免自动装箱和拆箱的开销,从而提高性能。在处理基本类型数据时,应该优先考虑使用基本类型流。

4.4 使用map和flatMap方法

map和flatMap方法可以将数据流中的元素映射到另一个流中,从而进行进一步的处理。这两种方法可以大大简化数据处理的过程,从而提高性能。在使用map和flatMap方法时,应该尽可能地避免创建过多的中间流,可以通过链式调用的方式将多个转换操作合并到一个流中,从而减少中间流的创建。

4.5 使用limit和skip方法

limit和skip方法可以控制数据流中元素的数量,从而提高性能。在处理大量数据时,可以使用limit方法限制数据流中元素的数量,从而避免处理过多的数据;在处理大数据集时,可以使用skip方法跳过前面的元素,从而提高处理效率。

5.总结

Java Stream是一种高效且易于使用的API,它可以大大简化数据处理的过程,从而提高开发效率。在使用Java Stream时,应该了解它的基本概念和使用方法,同时注意性能优化,从而更好地应用它来处理数据。