函数式编程(四)Stream流使用

一、概述

在使用stream之前,先理解Optional

OptionalJava 8引入的一个容器类,用于处理可能为空的值。它提供了一种优雅的方式来处理可能存在或不存在的值,避免了空指针异常。

Optional的主要特点如下:

  • 可能为空:Optional可以包含一个非空的值,也可以表示为空。
  • 避免空指针异常:通过使用Optional,可以避免在访问可能为空的值时出现空指针异常。
  • 显式判断:使用Optional需要显式地判断值是否存在,以便进行相应的处理。
  • 函数式操作:Optional提供了一系列的函数式操作方法,如map()filter()orElse()等,方便对Optional中的值进行转换、过滤和默认值处理。

使用Stream可以对集合或数组中的元素进行各种转换、过滤和映射等操作,以实现更简洁、灵活和函数式的编程风格。下面是使用Stream的一般步骤:

  • 创建Stream:通过集合、数组、IO通道或Stream的静态方法来创建一个Stream对象。
  • 中间操作(Intermediate Operations):使用中间操作方法对Stream进行转换、过滤、映射等操作,返回一个新的Stream对象。常见的中间操作包括filter()map()sorted()distinct()等。
  • 终止操作(Terminal Operations):使用终止操作方法对Stream进行最终的计算或收集操作,返回一个结果或一个最终的集合。常见的终止操作包括forEach()collect()reduce()min()max()等。

二、Stream的创建

在Java中,可以使用多种方式来创建Stream对象。下面是一些常见的创建Stream的方法:

1. 通过集合创建Stream

通过集合创建Stream是一种常见的方式,Java8 中的 Collection 接口被扩展,提供两个获取流的方法 :

  • Stream stream() : 返回一个顺序流
  • Stream parallelStream() : 返回一个并行流
Stream<Integer> stream1 = Arrays.asList(1,2,3,4).stream();
Stream<Integer> stream2 = Arrays.asList(1,2,3,4).parallelStream();

2. 通过数组创建Stream

通过数组创建Stream可以使用Arrays.stream()方法来实现。该方法接受一个数组作为参数,并返回一个对应类型的Stream对象。

int[] array = {1, 2, 3, 4, 5};
IntStream stream = Arrays.stream(array);

3. 通过Stream的静态方法创建Stream

  • 通过Stream的静态方法,可以使用of()iterate()generate()等方法来创建Stream对象。
  • 使用IntStreamLongStreamDoubleStreamstatic方法创建有限流,可以使用of()range()rangeClosed()等方法来创建Stream对象。
  • 使用随机数类的ints()方法创建无限数值流
// 使用of()方法创建Stream
Stream<String> stream1 = Stream.of("apple", "banana", "orange");
// 使用iterate()方法创建Stream
Stream<Integer> stream2 = Stream.iterate(0, n -> n + 2).limit(10);
// 使用generate()方法创建Stream
Stream<Double> stream3 = Stream.generate(Math::random).limit(5);


// 使用of()方法创建IntStream
IntStream.of(new int[]{1, 2, 3});
// 使用range()方法创建IntStream
IntStream.range(1, 3);
// 使用rangeClosed()方法创建IntStream
IntStream.rangeClosed(1, 3);

// 使用随机数类的ints()方法创建无限数值流
Random random = new Random();
IntStream ints = random.ints();

4. 通过IO通道创建Stream

  • 使用BufferedReaderlines方法从文件中获得行的流
  • Files类的操作路径的方法,如listfindwalklines
// 使用BufferedReader的lines方法从文件中获得行的流
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream("file.txt")));
Stream<String> lines = bufferedReader.lines();

Path path = Paths.get("file.txt");
Stream<String> lines = Files.lines(path);

5. 通过其他类提供的创建Stream

  • BitSet数值流
  • Pattern 将字符串分隔成流
  • JarFile 读取jar文件流
// BitSet数值流
IntStream stream = new BitSet().stream();

// Pattern 将字符串分隔成流
Pattern pattern = compile(",");
Stream<String> stringStream = pattern.splitAsStream("a,b,c,d");
stringStream.forEach(System.out::println);

// JarFile 读取jar文件流
Stream<JarEntry> stream = new JarFile("").stream();

这些方法提供了不同的方式来创建Stream对象,可以根据具体的需求选择适当的创建方式。创建Stream后,可以使用Stream的中间操作和终止操作来对数据进行处理和操作。

需要注意的是,Stream对象是一次性使用的,一旦对Stream进行了终止操作,就不能再对同一个Stream进行其他操作。如果需要对同一组数据进行多个操作,可以创建多个Stream对象来实现。

三、中间操作

3.1 筛选(filter

筛选,是按照一定的规则校验流中的元素,将符合条件的元素提取到新的流中的操作。

filter()是Java Stream API中的一个中间操作方法,用于根据指定的条件过滤流中的元素。它接受一个Predicate函数式接口作为参数,该接口定义了一个用于判断元素是否满足条件的方法。

filter()方法的语法:Stream<T> filter(Predicate<? super T> predicate)

其中,T表示流中的元素类型,predicate表示用于判断元素是否满足条件的Predicate对象。

filter()方法的工作原理如下:

  • 对于流中的每个元素,filter()方法会调用传入的Predicate对象的test()方法,将当前元素作为参数传递给test()方法。
  • 如果test()方法返回true,则表示当前元素满足条件,会被保留在新的流中。
  • 如果test()方法返回false,则表示当前元素不满足条件,会被过滤掉,不包含在新的流中。

下面是一个示例代码,演示了如何使用filter()方法过滤出偶数:

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

public class StreamFilterExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // 使用filter()方法过滤出偶数
        List<Integer> evenNumbers = numbers.stream()
                .filter(n -> n % 2 == 0)
                .collect(Collectors.toList());

        // 输出结果
        System.out.println(evenNumbers); // [2, 4, 6, 8, 10]
    }
}

在上面的示例中,我们首先创建了一个包含整数的列表。然后,我们使用filter()方法过滤出偶数,通过传入的Lambda表达式判断元素是否为偶数。最后,我们使用collect()方法将过滤后的结果收集到一个新的列表中,并输出结果。

通过使用filter()方法,我们可以根据指定的条件过滤流中的元素,只保留满足条件的元素,实现对数据的筛选和过滤操作。

3.2 元素转换(peek/map/flatMap)

在Java的Stream API中,peek()map()flatMap()是常用的中间操作方法,用于对流中的元素进行转换、处理和操作。

peek()方法:

  • peek()方法接受一个Consumer函数式接口作为参数,对流中的每个元素执行指定的操作,并返回一个新的流。
  • peek()方法可以用于调试和观察流中的元素,但不会改变流中元素的内容。
  • peek()方法是一个中间操作,它返回的是与原始流相同类型的新流。

map()方法:

  • map()方法接受一个Function函数式接口作为参数,将流中的每个元素映射为另一个元素,并返回一个新的流。
  • map()方法可以用于对流中的元素进行转换、提取或计算等操作。
  • map()方法是一个中间操作,它返回的是与原始流中元素类型不同的新流。

flatMap()方法:

  • flatMap()方法接受一个Function函数式接口作为参数,将流中的每个元素映射为一个流,并将所有流连接成一个新的流。
  • flatMap()方法可以用于扁平化嵌套的流结构,将多个流合并为一个流。
  • flatMap()方法是一个中间操作,它返回的是与原始流中元素类型不同的新流。

下面是一个示例代码,演示了peek()、map()和flatMap()的使用:

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

public class StreamOperationsExample {
    public static void main(String[] args) {
        List<String> words = Arrays.asList("Hello", "World", "Java");

        // 使用peek()方法调试和观察流中的元素
        List<String> peekResult = words.stream()
                .peek(System.out::println)
                .collect(Collectors.toList());

        // 使用map()方法将每个单词转换为大写
        List<String> mapResult = words.stream()
                .map(String::toUpperCase)
                .collect(Collectors.toList());

        // 使用flatMap()方法将每个单词拆分为字母
        List<String> flatMapResult = words.stream()
                .flatMap(word -> Arrays.stream(word.split("")))
                .collect(Collectors.toList());

        // 输出结果
        System.out.println("Peek Result: " + peekResult);
        System.out.println("Map Result: " + mapResult);
        System.out.println("FlatMap Result: " + flatMapResult);
    }
}

在上面的示例中,我们首先创建了一个包含单词的列表。然后,我们使用peek()方法对流中的元素进行调试和观察,并使用map()方法将每个单词转换为大写,最后使用flatMap()方法将每个单词拆分为字母。最终,我们使用collect()方法将结果收集到一个新的列表中,并输出结果。

通过使用peek()map()flatMap()等方法,我们可以对流中的元素进行转换、处理和操作,实现更加灵活和函数式的编程风格。

3.3 排序(sorted)

sorted()是Java Stream API中的一个中间操作方法,用于对流中的元素进行排序。它可以按照自然顺序或者通过自定义的Comparator来进行排序。

sorted()方法的语法:Stream<T> sorted() / Stream<T> sorted(Comparator<? super T> comparator)

其中,T表示流中的元素类型,comparator表示用于比较元素的Comparator对象。

sorted()方法的工作原理如下:

  • 对于无参的sorted()方法,它会使用元素的自然顺序进行排序。元素类型必须实现Comparable接口,否则会抛出ClassCastException
  • 对于带有Comparator参数的sorted()方法,它会使用指定的Comparator对象来进行排序。Comparator定义了元素之间的比较规则。

下面是一个示例代码,演示了如何使用sorted()方法对整数列表进行排序:

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

public class StreamSortedExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9, 3);

        // 使用sorted()方法对整数列表进行排序
        List<Integer> sortedNumbers = numbers.stream()
                .sorted()
                .collect(Collectors.toList());

        // 输出结果
        System.out.println(sortedNumbers); // [1, 2, 3, 5, 8, 9]
    }
}

在上面的示例中,我们首先创建了一个包含整数的列表。然后,我们使用sorted()方法对整数列表进行排序,默认按照自然顺序进行排序。最后,我们使用collect()方法将排序后的结果收集到一个新的列表中,并输出结果。

通过使用sorted()方法,我们可以对流中的元素进行排序,使得元素按照指定的顺序排列。可以根据需要使用自然顺序或自定义的Comparator来进行排序操作。

3.4 截取/跳过(limit/skip)

limit()skip()是Java Stream API中的两个中间操作方法,用于限制流中元素的数量。

limit()方法:

  • limit()方法接受一个long类型的参数,用于限制流中元素的数量。
  • 它返回一个新的流,其中包含原始流中的前n个元素(如果原始流中的元素不足n个,则返回所有元素)。

skip()方法:

  • skip()方法接受一个long类型的参数,用于跳过流中的前n个元素。
  • 它返回一个新的流,其中包含原始流中剩余的元素(如果原始流中的元素少于n个,则返回空流)。
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamLimitSkipExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        // 使用limit()方法限制流中元素的数量
        List<Integer> limitedNumbers = numbers.stream()
                .limit(3)
                .collect(Collectors.toList());

        // 使用skip()方法跳过流中的前N个元素
        List<Integer> skippedNumbers = numbers.stream()
                .skip(2)
                .collect(Collectors.toList());

        // 输出结果
        System.out.println(limitedNumbers); // [1, 2, 3]
        System.out.println(skippedNumbers); // [3, 4, 5]
    }
}

在上面的示例中,我们首先创建了一个包含整数的列表。然后,我们使用limit()方法限制流中元素的数量为3个,通过collect()方法将结果收集到一个新的列表中。接着,我们使用skip()方法跳过流中的前2个元素,同样通过collect()方法将结果收集到另一个新的列表中。最后,我们输出限制和跳过操作后的结果。

通过使用limit()skip()方法,我们可以对流中的元素进行数量的限制和跳过操作,实现对数据的筛选和截取。

3.5 合并(concat)

concat()是Java Stream API中的一个静态方法,用于将两个流连接起来形成一个新的流。它接受两个相同类型的流作为参数,并返回一个包含两个流中所有元素的新流。

concat()方法的语法:static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)

其中,T表示流中的元素类型,ab表示要连接的两个流。

concat()方法的工作原理如下:

  • 它会将第一个流的所有元素放在新流中,然后将第二个流的所有元素追加到新流的末尾。
  • 新流中的元素顺序与原始流的顺序保持一致。

下面是一个示例代码,演示了如何使用concat()方法连接两个流:

import java.util.stream.Stream;

public class StreamConcatExample {
    public static void main(String[] args) {
        Stream<Integer> stream1 = Stream.of(1, 2, 3);
        Stream<Integer> stream2 = Stream.of(4, 5, 6);

        // 使用concat()方法连接两个流
        Stream<Integer> concatenatedStream = Stream.concat(stream1, stream2);

        // 输出结果
        concatenatedStream.forEach(System.out::println); // 1, 2, 3, 4, 5, 6
    }
}

在上面的示例中,我们首先创建了两个整数流。然后,我们使用concat()方法将这两个流连接起来,形成一个新的流。最后,我们使用forEach()方法遍历并打印连接后的结果。

通过使用concat()方法,我们可以将两个流连接起来,形成一个包含两个流中所有元素的新流。这对于需要合并多个流的场景非常有用。

3.6 去重(distinct)

distinct()是Java Stream API中的一个中间操作方法,用于去除流中的重复元素。它会返回一个新的流,其中包含原始流中的所有不重复的元素。

distinct()方法使用元素的equals()方法来判断元素是否重复。如果两个元素相等,则只保留第一个出现的元素,后续出现的相同元素将被过滤掉。

distinct()方法的语法:Stream<T> distinct()

其中,T表示流中的元素类型。

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

public class StreamDistinctExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 2, 4, 3, 5);

        // 使用distinct()方法去除重复元素
        List<Integer> distinctNumbers = numbers.stream()
                .distinct()
                .collect(Collectors.toList());

        // 输出结果
        System.out.println(distinctNumbers); // [1, 2, 3, 4, 5]
    }
}

在上面的示例中,我们首先创建了一个包含整数的列表。然后,我们使用distinct()方法去除列表中的重复元素,并通过collect()方法将结果收集到一个新的列表中。最后,我们输出去除重复元素后的结果。

通过使用distinct()方法,我们可以方便地去除流中的重复元素,得到一个只包含不重复元素的新流。这对于数据去重和筛选操作非常有用。

四、终止操作

4.1 遍历/匹配(foreach/findAny/findFirst/anyMatch/allMatch/noneMatch

在Java Stream API中,forEach()findAny()findFirst()anyMatch()allMatch()noneMatch()是常用的终止操作方法,用于对流中的元素进行遍历、查找和匹配。

forEach()方法:

  • forEach()方法接受一个Consumer函数式接口作为参数,对流中的每个元素执行指定的操作。
  • 它没有返回值,只是对流中的每个元素进行操作。

findAny()findFirst()方法:

  • findAny()方法返回流中的任意一个元素,如果流为空则返回Optional.empty()
  • findFirst()方法返回流中的第一个元素,如果流为空则返回Optional.empty()

anyMatch()allMatch()noneMatch()方法:

  • anyMatch()方法接受一个Predicate函数式接口作为参数,判断流中是否存在满足指定条件的元素。
  • allMatch()方法接受一个Predicate函数式接口作为参数,判断流中的所有元素是否都满足指定条件。
  • noneMatch()方法接受一个Predicate函数式接口作为参数,判断流中是否没有任何元素满足指定条件。
  • 这些方法返回一个boolean类型的结果,表示是否存在满足条件的元素。
import java.util.Arrays;
import java.util.List;

public class StreamTerminalOperationsExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        // 使用forEach()方法遍历每个元素
        numbers.stream()
               .forEach(System.out::println);

        // 使用findAny()方法找到任意一个元素
        Integer anyNumber = numbers.stream()
                                   .findAny()
                                   .orElse(null);

        // 使用findFirst()方法找到第一个元素
        Integer firstNumber = numbers.stream()
                                     .findFirst()
                                     .orElse(null);

        // 使用anyMatch()方法判断是否存在大于3的元素
        boolean anyGreaterThanThree = numbers.stream()
                                             .anyMatch(n -> n > 3);

        // 使用allMatch()方法判断是否所有元素都小于10
        boolean allLessThanTen = numbers.stream()
                                        .allMatch(n -> n < 10);

        // 使用noneMatch()方法判断是否没有任何元素等于6
        boolean noneEqualsSix = numbers.stream()
                                       .noneMatch(n -> n == 6);

        // 输出结果
        System.out.println("Any Number: " + anyNumber);
        System.out.println("First Number: " + firstNumber);
        System.out.println("Any Greater Than Three: " + anyGreaterThanThree);
        System.out.println("All Less Than Ten: " + allLessThanTen);
        System.out.println("None Equals Six: " + noneEqualsSix);
    }
}

在上面的示例中,我们首先创建了一个包含整数的列表。然后,我们使用不同的终止操作方法对流中的元素进行遍历、查找和匹配操作,并输出结果。

通过使用这些终止操作方法,我们可以对流中的元素进行遍历、查找和匹配操作,得到相应的结果。

4.2 聚合(max/min/count)

在Java Stream API中,max()min()count()是常用的终止操作方法,用于获取流中的最大值、最小值和元素数量。

max()方法:

  • max()方法接受一个Comparator函数式接口作为参数,用于比较流中的元素。
  • 它返回流中的最大元素,如果流为空则返回Optional.empty()

min()方法:

  • min()方法接受一个Comparator函数式接口作为参数,用于比较流中的元素。
  • 它返回流中的最小元素,如果流为空则返回Optional.empty()

count()方法:

  • count()方法返回流中的元素数量,返回一个long类型的结果。
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class StreamTerminalOperationsExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        // 使用max()方法找到最大值
        Optional<Integer> maxNumber = numbers.stream()
                                             .max(Integer::compare);

        // 使用min()方法找到最小值
        Optional<Integer> minNumber = numbers.stream()
                                             .min(Integer::compare);

        // 使用count()方法计算元素数量
        long count = numbers.stream()
                            .count();

        // 输出结果
        System.out.println("Max Number: " + maxNumber.orElse(null));
        System.out.println("Min Number: " + minNumber.orElse(null));
        System.out.println("Count: " + count);
    }
}

在上面的示例中,我们首先创建了一个包含整数的列表。然后,我们使用不同的终止操作方法对流中的元素进行最大值、最小值和计数操作,并输出结果。

通过使用这些终止操作方法,我们可以方便地获取流中的最大值、最小值和元素数量。需要注意的是,max()min()方法返回的是Optional类型的结果,因为流中可能为空。

4.3 归约(reduce)

reduce()是Java Stream API中的一个终止操作方法,用于将流中的元素按照指定的规约操作进行合并,返回一个最终的结果。归约,也称缩减,顾名思义,是把一个流缩减成一个值,能实现对集合求和、求乘积和求最值操作。

reduce()方法接受一个BinaryOperator函数式接口作为参数,该接口定义了一个二元操作,用于将两个元素进行合并。它还可以接受一个初始值作为累加器的初始值。

reduce()方法的语法:

Optional<T> reduce(BinaryOperator<T> accumulator)

T reduce(T identity, BinaryOperator<T> accumulator)

其中,T表示流中的元素类型,accumulator表示用于合并元素的操作,identity表示累加器的初始值。

reduce()方法的工作原理如下:

  • 对于流中的第一个元素,将其作为累加器的初始值。
  • 对于后续的每个元素,使用累加器和当前元素进行合并操作,得到一个新的累加器值。
  • 最终返回合并后的累加器值。
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);

        // 使用reduce()方法对整数列表进行求和
        Optional<Integer> sum = numbers.stream()
                                       .reduce(Integer::sum);

        // 输出结果
        System.out.println("Sum: " + sum.orElse(0));
    }
}

在上面的示例中,我们首先创建了一个包含整数的列表。然后,我们使用reduce()方法对整数列表进行求和,通过传入的Integer::sum方法作为累加器进行合并操作。最后,我们使用orElse()方法获取求和结果,并输出结果。

通过使用reduce()方法,我们可以对流中的元素进行合并操作,实现对数据的聚合和规约。需要注意的是,reduce()方法返回的是Optional类型的结果,因为流中可能为空。

4.4 收集(collect)

collect,收集,就是把一个流收集起来,最终可以是收集成一个值也可以收集成一个新的集合。

collect()用于将流中的元素收集到一个集合或其他数据结构中。

collect()方法接受一个Collector对象作为参数,该对象定义了如何将流中的元素进行收集和组合。Collector接口提供了一系列静态方法来创建常见的收集器实例。

下面是collect()方法的语法:<R> R collect(Collector<? super T, A, R> collector)

其中,T表示流中的元素类型,A表示中间结果的类型,R表示最终结果的类型。

collect()方法的工作原理如下:

  • 它会使用Collector对象中定义的逻辑,对流中的元素进行收集和组合。
  • 最终返回一个包含收集结果的对象,可以是ListSetMap等。
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamCollectExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        // 使用collect()方法将偶数收集到一个新的列表中
        List<Integer> evenNumbers = numbers.stream()
                                           .filter(n -> n % 2 == 0)
                                           .collect(Collectors.toList());

        // 输出结果
        System.out.println(evenNumbers); // [2, 4]
    }
}

在上面的示例中,我们首先创建了一个包含整数的列表。然后,我们使用filter()方法过滤出偶数,并通过collect()方法将结果收集到一个新的列表中。最后,我们输出收集后的结果。

通过使用collect()方法,我们可以方便地将流中的元素收集到一个集合或其他数据结构中,实现对数据的聚合和收集操作。

4.4.1 归集(toList/toSet/toMap)

因为流不存储数据,那么在流中的数据完成处理后,需要将流中的数据重新归集到新的集合里。toListtoSettoMap比较常用,另外还有toCollectiontoConcurrentMap等复杂一些的用法。

在Java Stream API中,有多个方法可用于将流中的元素收集到不同类型的集合或映射中。

  1. toList()方法:
    • toList()方法将流中的元素收集到一个列表中。
    • 它返回一个包含流中所有元素的新列表。
  1. toSet()方法:
    • toSet()方法将流中的元素收集到一个集合中。
    • 它返回一个包含流中所有元素的新集合。
  1. toMap()方法:
    • toMap()方法将流中的元素收集到一个映射中。
    • 它接受两个Function函数式接口作为参数,用于提取键和值,并返回一个包含流中所有键值对的新映射。
  1. toCollection()方法:
    • toCollection()方法将流中的元素收集到指定类型的集合中。
    • 它接受一个Supplier函数式接口作为参数,用于提供一个自定义的集合实例。
  1. toConcurrentMap()方法:
    • toConcurrentMap()方法将流中的元素收集到一个并发映射中。
    • 它接受三个Function函数式接口作为参数,用于提取键、值和处理键冲突的方式。

这些方法都是终止操作,它们根据需要将流中的元素收集到不同类型的集合或映射中。根据具体的需求和数据结构,可以选择适当的方法来进行元素的收集和聚合操作。

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

public class StreamCollectExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        // 使用toList()方法将元素收集到列表中
        List<Integer> list = numbers.stream()
                                    .collect(Collectors.toList());

        // 使用toSet()方法将元素收集到集合中
        Set<Integer> set = numbers.stream()
                                  .collect(Collectors.toSet());

        // 使用toMap()方法将元素收集到映射中
        Map<Integer, String> map = numbers.stream()
                                          .collect(Collectors.toMap(
                                              num -> num,
                                              num -> "Value" + num
                                          ));

        // 使用toCollection()方法将元素收集到自定义集合中
        Set<Integer> customSet = numbers.stream()
                                        .collect(Collectors.toCollection(() -> new CustomSet<>()));

        // 使用toConcurrentMap()方法将元素收集到并发映射中
        Map<Integer, String> concurrentMap = numbers.stream()
                                                    .collect(Collectors.toConcurrentMap(
                                                        num -> num,
                                                        num -> "Value" + num,
                                                        (v1, v2) -> v1 + ", " + v2,
                                                        ConcurrentHashMap::new
                                                    ));

        // 输出结果
        System.out.println("List: " + list); // [1, 2, 3, 4, 5]
        System.out.println("Set: " + set); // [1, 2, 3, 4, 5]
        System.out.println("Map: " + map); // {1=Value1, 2=Value2, 3=Value3, 4=Value4, 5=Value5}
        System.out.println("Custom Set: " + customSet);
        System.out.println("Concurrent Map: " + concurrentMap);
    }
}

class CustomSet<T> extends HashSet<T> {
    // 自定义集合类
}

通过使用toList()toSet()toMap()等方法,我们可以方便地将流中的元素收集到列表、集合或映射中,实现对数据的聚合和收集操作。

通过使用toCollection()toConcurrentMap()等方法,我们可以方便地将流中的元素收集到自定义的集合或并发映射中,实现对数据的聚合和收集操作。

4.4.2 统计(count/averaging/max(min)/summing/summarizing)

在Java Stream API中,有多个终止操作方法可以用于对流中的元素进行计数、平均值、最大值、最小值、求和和统计等操作。

  1. count()方法:
    • count()方法返回流中的元素数量。
    • 它返回一个long类型的结果,表示流中的元素数量。
  1. averagingXxx()方法:
    • averagingXxx()方法用于计算流中元素的平均值,其中Xxx可以是IntLongDouble
    • 它返回一个double类型的结果,表示流中元素的平均值。
  1. maxBy()minBy()方法:
    • maxBy()方法接受一个Comparator函数式接口作为参数,返回流中的最大元素。
    • minBy()方法接受一个Comparator函数式接口作为参数,返回流中的最小元素。
    • 它们返回一个Optional对象,表示流中的最大或最小元素。
  1. summingXxx()方法:
    • summingXxx()方法用于对流中的元素进行求和,其中Xxx可以是IntLongDouble
    • 它返回一个Xxx类型的结果,表示流中元素的总和。
  1. summarizingXxx()方法:
    • summarizingXxx()方法用于对流中的元素进行统计,其中Xxx可以是IntLongDouble
    • 它返回一个包含元素数量、总和、平均值、最大值和最小值的XxxSummaryStatistics对象。
import java.util.Arrays;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

public class StreamStatisticsExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        // 使用count()方法统计元素数量
        long count = numbers.stream()
                            .count();

        // 使用averagingXxx()方法计算平均值
        double average = numbers.stream()
                                .collect(Collectors.averagingInt(Integer::intValue));

        // 使用maxBy()方法找到最大值
        Optional<Integer> max = numbers.stream()
                                       .max(Integer::compare);

        // 使用minBy()方法找到最小值
        Optional<Integer> min = numbers.stream()
                                       .min(Integer::compare);

        // 使用summingXxx()方法求和
        int sum = numbers.stream()
                         .collect(Collectors.summingInt(Integer::intValue));

        // 使用summarizingXxx()方法进行统计
        IntSummaryStatistics statistics = numbers.stream()
                                                 .collect(Collectors.summarizingInt(Integer::intValue));

        // 输出结果
        System.out.println("Count: " + count);
        System.out.println("Average: " + average);
        System.out.println("Max: " + max.orElse(null));
        System.out.println("Min: " + min.orElse(null));
        System.out.println("Sum: " + sum);
        System.out.println("Statistics: " + statistics);
    }
}

在上面的示例中,我们首先创建了一个包含整数的列表。然后,我们使用不同的终止操作方法对流中的元素进行计数、平均值、最大值、最小值、求和和统计操作,并输出结果。

通过使用这些终止操作方法,我们可以方便地对流中的元素进行各种统计操作,得到相应的结果。

4.4.3 分组(partitioningBy/groupingBy)

在Java Stream API中,partitioningBy()groupingBy()是用于对流中的元素进行分区和分组的收集器。

partitioningBy()方法:

  • partitioningBy()方法接受一个Predicate函数式接口作为参数,用于将流中的元素分为满足条件和不满足条件的两个部分。
  • 它返回一个Map<Boolean, List<T>>类型的结果,其中Boolean表示分区的键,List<T>表示满足或不满足条件的元素列表。

groupingBy()方法:

  • groupingBy()方法接受一个Function函数式接口作为参数,用于根据指定的分类函数对流中的元素进行分组。
  • 它返回一个Map<K, List<T>>类型的结果,其中K表示分组的键,List<T>表示具有相同键的元素列表。
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class StreamPartitioningGroupingExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);

        // 使用partitioningBy()方法将奇偶数分区
        Map<Boolean, List<Integer>> partitionedNumbers = numbers.stream()
                                                                 .collect(Collectors.partitioningBy(n -> n % 2 == 0));

        // 使用groupingBy()方法将数字按照奇偶分组
        Map<String, List<Integer>> groupedNumbers = numbers.stream()
                                                           .collect(Collectors.groupingBy(n -> n % 2 == 0 ? "Even" : "Odd"));

        // 输出结果
        System.out.println("Partitioned Numbers: " + partitionedNumbers);
        System.out.println("Grouped Numbers: " + groupedNumbers);
    }
}

在上面的示例中,我们首先创建了一个包含整数的列表。然后,我们使用partitioningBy()方法将列表中的元素按照奇偶数进行分区,使用groupingBy()方法将列表中的元素按照奇偶进行分组。最后,我们输出分区和分组的结果。

通过使用partitioningBy()groupingBy()方法,我们可以方便地对流中的元素进行分区和分组操作,实现对数据的分类和归类。

4.4.4 接合(joining)

在Java Stream API中,joining()是一个终止操作方法,用于将流中的元素连接成一个字符串。

joining()方法没有参数,它返回一个包含流中所有元素连接后的字符串。默认情况下,元素之间使用空字符串作为分隔符进行连接,但也可以通过提供自定义的分隔符来指定连接时使用的分隔符。

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

public class StreamJoiningExample {
    public static void main(String[] args) {
        List<String> words = Arrays.asList("Hello", "World", "Java");

        // 使用joining()方法将字符串列表中的元素连接成一个字符串
        String result = words.stream()
                             .collect(Collectors.joining());

        // 输出结果
        System.out.println(result); // HelloWorldJava

        // 使用自定义分隔符连接字符串列表中的元素
        String customResult = words.stream()
                                   .collect(Collectors.joining(", "));

        // 输出结果
        System.out.println(customResult); // Hello, World, Java
    }
}

在上面的示例中,我们首先创建了一个包含字符串的列表。然后,我们使用joining()方法将列表中的元素连接成一个字符串,默认使用空字符串作为分隔符。接着,我们使用自定义的分隔符" , "来连接字符串列表中的元素。最后,我们输出连接后的结果。

通过使用joining()方法,我们可以方便地将流中的元素连接成一个字符串,实现对数据的合并和拼接操作。

4.4.5 归约(reducing)

在Java Stream API中,reducing()是一个终止操作方法,用于将流中的元素按照指定的规约操作进行合并,返回一个最终的结果。

reducing()方法接受一个初始值和一个BinaryOperator函数式接口作为参数,该接口定义了一个二元操作,用于将两个元素进行合并。它还可以接受一个Function函数式接口,用于将流中的元素转换为另一种类型。

下面是reducing()方法的语法:

Optional<T> reducing(BinaryOperator<T> accumulator)

T reducing(T identity, BinaryOperator<T> accumulator)

<U> U reducing(U identity, Function<? super T, ? extends U> mapper, BinaryOperator<U> accumulator)

其中,T表示流中的元素类型,U表示结果的类型,accumulator表示用于合并元素的操作,identity表示初始值,mapper表示元素转换的函数。

reducing()方法的工作原理如下:

  • 对于流中的第一个元素,将其作为累加器的初始值。
  • 对于后续的每个元素,使用累加器和当前元素进行合并操作,得到一个新的累加器值。
  • 最终返回合并后的累加器值。
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class StreamReducingExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        // 使用reducing()方法对整数列表进行求和
        Optional<Integer> sum = numbers.stream()
                                       .reduce(Integer::sum);

        // 输出结果
        System.out.println("Sum: " + sum.orElse(0));
    }
}

在上面的示例中,我们首先创建了一个包含整数的列表。然后,我们使用reduce()方法对整数列表进行求和,通过传入的Integer::sum方法作为累加器进行合并操作。最后,我们使用orElse()方法获取求和结果,并输出结果。

通过使用reducing()方法,我们可以对流中的元素进行合并操作,实现对数据的聚合和规约。需要注意的是,reduce()方法返回的是Optional类型的结果,因为流中可能为空。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值