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

Java Stream 流创建方式及应用场景

2024-02-222.3k 阅读

Java Stream 流概述

在Java 8引入Stream API之前,处理集合数据通常需要编写冗长且复杂的循环代码。Stream API提供了一种更简洁、更高效、更易读的方式来处理集合数据,使得代码更具声明性而非命令性。

Stream可以看作是一个支持顺序和并行聚合操作的元素序列。它并不是数据结构,不存储数据,而是在现有数据结构(如集合、数组等)上进行操作。Stream操作通常分为中间操作(intermediate operations)和终端操作(terminal operations)。中间操作返回一个新的Stream,允许链式调用多个中间操作;终端操作会触发Stream的处理并返回结果。

Java Stream 流的创建方式

1. 通过集合创建Stream

集合接口(如ListSet等)在Java 8中新增了stream()parallelStream()方法来创建Stream。

  • 顺序流:使用stream()方法创建顺序流,它按照元素的顺序依次处理。
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);
        numbers.add(4);
        numbers.add(5);

        Stream<Integer> sequentialStream = numbers.stream();
        sequentialStream.forEach(System.out::println);
    }
}

在上述代码中,通过numbers.stream()创建了一个顺序流,并使用forEach终端操作打印出流中的每个元素。

  • 并行流:使用parallelStream()方法创建并行流,它会利用多核处理器并行处理元素,在处理大数据集时通常能提高性能。
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class ParallelStreamCreationFromCollection {
    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);

        Stream<Integer> parallelStream = numbers.parallelStream();
        parallelStream.forEach(System.out::println);
    }
}

这里通过numbers.parallelStream()创建了并行流。需要注意的是,并行流中元素的处理顺序是不确定的,因为多个线程可能同时处理不同的元素。

2. 通过数组创建Stream

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, 4, 5};
        // 对于基本类型数组,创建的是IntStream
        java.util.stream.IntStream intStream = Arrays.stream(intArray);
        intStream.forEach(System.out::println);

        String[] stringArray = {"apple", "banana", "cherry"};
        Stream<String> stringStream = Arrays.stream(stringArray);
        stringStream.forEach(System.out::println);
    }
}

对于基本类型数组,会创建对应的IntStreamLongStreamDoubleStream等,而对象数组则创建Stream

3. 使用Stream.of()方法创建Stream

Stream类的of()方法可以直接创建包含指定元素的Stream。

import java.util.stream.Stream;

public class StreamCreationUsingOf {
    public static void main(String[] args) {
        Stream<Integer> streamFromOf = Stream.of(1, 2, 3, 4, 5);
        streamFromOf.forEach(System.out::println);

        Stream<String> stringStreamFromOf = Stream.of("apple", "banana", "cherry");
        stringStreamFromOf.forEach(System.out::println);
    }
}

这种方式适用于创建包含少量固定元素的Stream。

4. 使用Stream.generate()方法创建无限Stream

generate()方法接受一个Supplier接口的实现,该接口的get()方法会不断生成新的元素,从而创建一个无限Stream。通常需要结合limit()等终端操作来限制流的长度。

import java.util.Random;
import java.util.stream.Stream;

public class InfiniteStreamGeneration {
    public static void main(String[] args) {
        Stream<Double> randomStream = Stream.generate(() -> new Random().nextDouble());
        randomStream.limit(5).forEach(System.out::println);
    }
}

在上述代码中,Stream.generate(() -> new Random().nextDouble())创建了一个无限的Stream,其中每个元素都是一个随机生成的double值。通过limit(5)将流的长度限制为5个元素。

5. 使用Stream.iterate()方法创建无限Stream

iterate()方法接受一个初始值和一个UnaryOperator接口的实现,它会从初始值开始,不断应用UnaryOperator来生成新的元素,从而创建一个无限Stream。同样,通常需要结合limit()等终端操作。

import java.util.stream.Stream;

public class InfiniteStreamIteration {
    public static void main(String[] args) {
        Stream<Integer> evenNumbers = Stream.iterate(0, n -> n + 2);
        evenNumbers.limit(5).forEach(System.out::println);
    }
}

这里Stream.iterate(0, n -> n + 2)从初始值0开始,每次应用n -> n + 2生成下一个偶数,通过limit(5)只获取前5个偶数。

Java Stream 流的应用场景

1. 数据过滤

在处理集合数据时,经常需要根据某些条件筛选出符合要求的元素。Stream的filter()方法可以实现这一功能。

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

public class StreamFiltering {
    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> evenNumbers = numbers.stream()
               .filter(n -> n % 2 == 0)
               .collect(Collectors.toList());
        System.out.println(evenNumbers);
    }
}

在上述代码中,filter(n -> n % 2 == 0)筛选出了列表中的偶数,并通过collect(Collectors.toList())将这些偶数收集到一个新的列表中。

2. 数据映射

有时候需要将流中的元素按照某种规则进行转换,这可以通过map()方法实现。

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

public class StreamMapping {
    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> squaredNumbers = numbers.stream()
               .map(n -> n * n)
               .collect(Collectors.toList());
        System.out.println(squaredNumbers);
    }
}

这里map(n -> n * n)将流中的每个元素平方,然后收集到一个新的列表中。

3. 数据聚合

Stream API提供了丰富的方法来对数据进行聚合操作,如sum()average()max()min()等。

import java.util.ArrayList;
import java.util.List;
import java.util.OptionalDouble;

public class StreamAggregation {
    public static void main(String[] args) {
        List<Double> numbers = new ArrayList<>();
        numbers.add(1.5);
        numbers.add(2.5);
        numbers.add(3.5);

        OptionalDouble average = numbers.stream()
               .mapToDouble(Double::doubleValue)
               .average();
        average.ifPresent(System.out::println);
    }
}

在上述代码中,通过mapToDouble(Double::doubleValue)Stream<Double>转换为DoubleStream,然后使用average()方法计算平均值。OptionalDouble用于处理可能不存在平均值的情况(如空流)。

4. 数据分组

可以根据某个属性对数据进行分组,这在数据分析和统计中非常有用。

import java.util.*;
import java.util.stream.Collectors;

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;
    }
}

public class StreamGrouping {
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
                new Person("Alice", 25),
                new Person("Bob", 30),
                new Person("Charlie", 25)
        );

        Map<Integer, List<Person>> peopleByAge = people.stream()
               .collect(Collectors.groupingBy(Person::getAge));
        System.out.println(peopleByAge);
    }
}

这里Collectors.groupingBy(Person::getAge)根据Person对象的年龄对人员进行分组,返回一个Map,其中键是年龄,值是具有该年龄的人员列表。

5. 并行处理

当处理大数据集时,并行流可以显著提高处理速度。例如,计算1到1000000的整数之和。

public class ParallelStreamExample {
    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();
        long sumSequential = java.util.stream.LongStream.rangeClosed(1, 1000000)
               .sum();
        long endTime = System.currentTimeMillis();
        System.out.println("Sequential sum: " + sumSequential + " in " + (endTime - startTime) + " ms");

        startTime = System.currentTimeMillis();
        long sumParallel = java.util.stream.LongStream.rangeClosed(1, 1000000)
               .parallel()
               .sum();
        endTime = System.currentTimeMillis();
        System.out.println("Parallel sum: " + sumParallel + " in " + (endTime - startTime) + " ms");
    }
}

在上述代码中,通过并行流计算总和通常会比顺序流更快,尤其是在多核处理器上。但需要注意,并行流并不总是比顺序流快,对于小数据集或者复杂的计算逻辑,并行流的开销可能会超过其带来的性能提升。

6. 查找与匹配

Stream API提供了findFirst()findAny()anyMatch()allMatch()noneMatch()等方法用于查找和匹配元素。

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

public class StreamSearchAndMatch {
    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);

        Optional<Integer> firstEven = numbers.stream()
               .filter(n -> n % 2 == 0)
               .findFirst();
        firstEven.ifPresent(System.out::println);

        boolean hasEven = numbers.stream()
               .anyMatch(n -> n % 2 == 0);
        System.out.println("Has even number: " + hasEven);
    }
}

findFirst()返回流中的第一个匹配元素,用Optional包装以处理可能不存在匹配元素的情况。anyMatch()检查流中是否至少有一个元素匹配给定条件。

7. 去重与排序

可以使用distinct()方法去除流中的重复元素,使用sorted()方法对流中的元素进行排序。

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

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

        List<Integer> distinctAndSorted = numbers.stream()
               .distinct()
               .sorted()
               .collect(Collectors.toList());
        System.out.println(distinctAndSorted);
    }
}

在上述代码中,distinct()去除重复的数字,sorted()对剩余的数字进行升序排序。

8. 复杂数据处理与组合操作

Stream API的强大之处在于可以将多个操作组合起来,以实现复杂的数据处理逻辑。例如,从一个包含人员信息的列表中,筛选出年龄大于30岁的人员,按照姓名排序,并将他们的姓名收集到一个新的列表中。

import java.util.*;
import java.util.stream.Collectors;

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;
    }
}

public class ComplexStreamOperations {
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
                new Person("Alice", 25),
                new Person("Bob", 35),
                new Person("Charlie", 32),
                new Person("David", 28)
        );

        List<String> namesOfOlderPeople = people.stream()
               .filter(p -> p.getAge() > 30)
               .sorted(Comparator.comparing(Person::getName))
               .map(Person::getName)
               .collect(Collectors.toList());
        System.out.println(namesOfOlderPeople);
    }
}

在这个例子中,依次使用了filter()sorted()map()collect()方法,展示了Stream API在复杂数据处理场景下的灵活性和强大功能。

通过以上各种创建方式和丰富的应用场景,Java Stream API为开发者提供了一种高效、简洁且功能强大的处理集合数据的方式,无论是简单的数据过滤和映射,还是复杂的数据聚合和分组,都能轻松应对。在实际开发中,合理运用Stream可以提高代码的可读性和性能。