Stream API

掌握了Lambda表达式和方法引用之后,我们就可以很轻松的 Stream API 了,没有掌握Lambda表达式和方法引用的话,建议去看看我之前写的文章。因为接下来会大量的用到它们。

Stream

什么是Stream
Stream是一组用来处理数组、集合的API,它支持顺序流和并行流的聚合操作。

Stream特性

  • 不是数据结构,没有内部存储。
  • 不支持索引访问。
  • 延迟计算。
  • 支持并行。
  • 很容易生成数组或集合(ListSet)。
  • 支持过滤、查找、转换、汇总、聚合等操作。

备注:
1、在没有执行终止操作之前,它是不会进行计算的。

Stream运行机制

Stream 分为数据源,中间操作,终止操作。
数据源可以是一个数组、一个集合、一个生成器方法、一个I/O通道等等。
一个流可以有零个或者多个中间操作,每一个中间操作都会返回一个新的流,供下一个操作使用。一个流只会有一个终止操作。
Stream 只有遇到终止操作,它的数据源才开始执行遍历操作。

Stream常用的API

中间操作

  • 过滤 filter
  • 去重 distinct
  • 排序 sorted
  • 截取 limitskip
  • 转换 mapflatMap
  • 其他 peek

终止操作

  • 循环 forEach
  • 计算 minmaxcountaverage
  • 匹配 anyMatchallMatchnoneMatchfindFirstfindAny
  • 汇聚 reduce
  • 收集器 toArraycollect

Stream的创建

  • 通过数组来创建。
  • 通过集合来创建。
  • 通过 Stream.generate 方法来创建。
  • 通过 Stream.iterate 方法来创建。
  • 通过其他API来创建(文件、字符串等等)。

这里说明一下,Stream API 在Android使用的话,要求API 24 以上才能使用,也可以导入com.annimon:stream:$lastVersion库来使用它。
了解了一下基本的概念后,下面我们来看看Stream API 实战。

Stream实战

首先我们看看用代码如何来实现Stream的创建。
1、通过数组来创建。

public static Stream of(T... values) {
    ...
}

String[] arr = {"a", "b", "c", "1", "2"};
Stream stream = Stream.of(arr);

使用Stream.of方法将数组作为参数传入,即可得到 Stream

2、通过集合来创建。

default Stream stream() {
        ...
}

List list = Arrays.asList("a", "b", "c", "1", "2");
Stream stream = list.stream();

使用list.stream方法创建得到Stream

3、通过Stream.generate方法来创建。

public static Stream generate(Supplier s) {
    ...
}

Stream stream = Stream.generate(() -> 1);

使用Stream.generate方法创建得到Stream时,传入的是Supplier,它代表一个输出,说明调用Stream.generate方法会不断的输入一个值,放到Stream中。对于Stream.generate方法的使用,一般都会和一些中间操作的API搭配使用,比如使用 limit 截取一部分数据。

4、通过Stream.iterate方法来创建。

public static Stream iterate(final T seed, final UnaryOperator f) {
        ...
}

Stream stream = Stream.iterate(1, x -> x + 1);

使用Stream.iterate方法创建得到Stream时,传入的是一个初始值和一个一元运算(输入和输出类型相同)的Lambda表达式。上面的操作解释为输入一个初始值为1,每次的输入结果为上一次输出的值加1,然后不然循环输入。

备注:Stream.generateStream.iterate 方法生成的是一个无止尽的 Stream ,我们在使用的时候需要注意这一点。

5、通过其他API来创建。

String str = "abcd";
IntStream stream = str.chars();

Stream stream = Files.lines(Paths.get("path"));

我们也可以使用一些其他API自带的创建 Stream 的方法。

接下来看看 Stream 常用的API的使用。
1、中间操作符。

Stream filter(Predicate predicate);

Stream distinct();

Stream sorted();

Stream limit(long maxSize);

Stream skip(long n);

 Stream map(Function mapper);

 Stream flatMap(Function> mapper);

Stream peek(Consumer action);

S parallel();

S sequential();

2、终止操作符。

void forEach(Consumer action);

Optional min(Comparator comparator);

Optional max(Comparator comparator);

long count();

OptionalDouble average();

boolean anyMatch(Predicate predicate);

boolean allMatch(Predicate predicate);

boolean noneMatch(Predicate predicate);

Optional findFirst();

Optional findAny();

T reduce(T identity, BinaryOperator accumulator);

Object[] toArray();

 R collect(Collector collector);

看到这些方法是不是很眼熟???
没错,在讲Lambda表达式和函数式接口的时候,我们就列出过一些函数式接口的表格,我再把它贴出来吧,不是凑字数哦!

接口名 参数 返回值 用途 含义
Predicate T boolean 断言 代表一个输入
Consumer T void 消费 代表一个输入
Function T R 函数 代表一个输入,一个输出(一般输入和输出是不同类型的)
BiFunction (T,U) R 函数 代表两个输入,一个输出(一般输入和输出是不同类型的)
Supplier None T 工厂方法 代表一个输出
UnaryOperator T T 逻辑非、迭代器 代表一个输入,一个输出(输入和输出是相同类型的)
BinaryOperator (T,T) T 二元操作 代表两个输入,一个输出(输入和输出是相同类型的)

不知道有没有人和我一样,第一次学习Lambda表达式的时候,根本不理解这些函数式接口的用途代表什么意思。比如什么是“断言”,什么是“消费”等等。

所以,我们还是有必要了解一下这些概念的,这对于我们之后的学习和学术讨论很有帮助。

断言:其实是防止程序意外出错的一种宏,如果其参数计算为假,则程序发出警告,且退出。
最常见的用法就是在函数入口处保证输入参数的正确性。

assert(object != NULL) ;

消费:是指程序在运行过程中,资源的消耗和使用。
最常用的用法就是将元素传递给其他函数处理,并且不给与返回值。

stream.forEach(System.out::println)

函数:也称函数关系式,是指给定元素,经过一些行为、处理后得到其他的元素的关系表达,它的核心就是对应关系。
函数的用途就很广了,尤其是我们学习函数式编程之后,才能真正体会它的强大。

stream.map {
    x -> x.toString
}

工厂方法:我们知道工厂是生成东西的工具,那么我们是不是可以理解为工厂方法是生成方法的工具呢?是的,说明白一点就是提供一些生成对象的方法给我们使用,它本身是不创建东西的,我们只需要关心方法如何使用

stream.collect(Collectors.toList())

可以看到 Collectors 类含有很多生成其他对象的方法,比如 toCollection()toSet()toList()toMap() 等等。

逻辑非:顾名思义,就是取一个有效值的反值。
使用场景多用于输入和输出的类型相同。

Stream.iterate(1, x -> !x)

二元操作:即为输入两个参数和输出的类型相同,多用于合并 Stream 的场景。

stream.reduce((value1, value2) ->
    value1 + value2
)

了解这些函数式接口之后,我们在使用 Stream API 的时候就很容易了,接下来我们来看看这些操作符的用法吧。

filter 过滤

filter中的条件,返回true的才会保留

// 过滤基数值,保留偶数值,最后遍历
Arrays.asList(1, 2, 3, 4, 5).stream().filter(x -> x%2 == 2).forEach(System.out::println);

distince 去重

distinct是调用元素的hashCode和equals方法判断重复的值或者对象,从而实现去重功能。
对于实体类去重时,需要重写实体类的 equals() 以及 hashCode() 方法
也可以使用 filtercollect 操作符去重。

//去重,barry存在2个,去重后只有一个
Stream.of("wally", "barry", "rose", "barry").distinct().forEach(System.out::println);
// filter 实体类字段去重
private static  Predicate distinctByKey(Function keyExtractor) {
    Set seen = ConcurrentHashMap.newKeySet();
    return t -> seen.add(keyExtractor.apply(t));
}
stre.filter(distinctByKey(User::getName)).forEach(System.out::println);
// collect 实体类字段去重
stream.collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(User::getName))), ArrayList::new)).forEach(System.out::println);
 
 

sorted 排序

默认按照自然顺序,从小到大排序。

Stream.of(1, 4, 8, 2, 0, -1).sorted().forEach(System.out::println);
// 倒序排序
Stream.of(1, 4, 8, 2, 0, -1).sorted((a, b) -> b - a).forEach(System.out::println);

limit 限制

限制取出 Stream 的个数。

Stream.iterate(1, x -> x + 1).limit(50).forEach(System.out::println);

skip 步长

跳过步长大小取值。

Stream.iterate(1, x -> x + 1).skip(5).limit(50).forEach(System.out::println);

map 数据转换

// 将Stream的每个元素进行大写转换
Stream.of("wally", "barry", "rose", "barry").map(String.toUpperCase).forEach(System.out::println);

flatMap 摊平

组合多个流为一个流

Stream.of(Arrays.asList("wally", "barry", "rose"), Arrays.asList("jack", "ben")).flatMap(names -> names.stream).forEach(System.out::println);

peek 查看

这个操作符主要目的是用于调试,查看 Stream 中的数据经过每个操作符后的状态。

Stream.of("wally", "barry", "rose", "barry")
    .peek(System.out::println)
    .map(String.toUpperCase)
    .peek(System.out::println)
    .forEach(System.out::println);

需要注意一点,peek是中间操作符,直接使用是无效的,不会打印任何东西。

parallel 并行

使用该操作符,会把当前 Stream 用并行方式处理。
设置并行 Stream 的线程数(包含主线程)。

System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "5")

sequential 串行(默认)

使用该操作符,会把当前 Stream 用串行方式处理。

forEach 遍历

Stream.of("wally", "barry", "rose", "barry").forEach(System.out::println);

min 取最小

int min = Stream.of(1, 2, 3, 4, 5, 6).min(Integer::compareTo).orElse(-1);

max 取最大

int max = Stream.of(1, 2, 3, 4, 5, 6).max(Integer::compareTo).orElse(-1);

count 计数

统计元素个数

long count = Stream.of(1, 2, 3, 4, 5, 6).count();

average 取平均

取平均只有IntStream、DoubleStream、LongStream中可以使用,也可以使用collect取平均值

Double averagingValue = Stream.of(1, 2, 3, 4, 5, 6).collect(Collectors.averagingInt(value -> value));

anyMatch 找任意匹配条件的值或对象

判断,只要一个匹配,就返回true

// 匹配长度大于或者等于 5,只要有一个匹配,即使 rose 的长度为 4 不符合也返回 true。
boolean result = Stream.of("wally", "barry", "rose").anyMatch(s -> s.length() >= 5);

allMatch 找所有匹配条件的值或对象

判断,所有都匹配,才返回true

// 匹配长度大于或者等于 5,因为 rese 的长度 4,所以返回false
boolean result = Stream.of("wally", "barry", "rose").allMatch(s -> s.length() >= 5);

noneMatch 找不符合匹配条件的值或对象

// 匹配长度大于或者等于 5,因为 wally、barry 的长度符合,所以返回false
boolean result = Stream.of("wally", "barry", "rose").noneMatch(s -> s.length() >= 5);

findFirst 找第一个值或对象

Optional first = Stream.of(1, 2, 3, 4, 5, 6).findFirst();

findAny 找任意的值或对象

Optional any = Stream.of(1, 2, 3, 4, 5, 6).findAny();

reduce 累积、结合

根据一定的规则将 Stream 中的元素进行计算后返回一个唯一的值。
有三种方法可以调用,分别是:

  • Optional reduce(BinaryOperator accumulator);
  • T reduce(T identity, BinaryOperator accumulator);
  • U reduce(U identity, BiFunction accumulator, BinaryOperator combiner);
    需要注意的是,在使用三个参数的方法时,如果 Stream 是非并行的,combiner 不会生效,计算过程和两个参数的方法是一致的。
// 方法一,二元运算处理,常用于求和、求最小值或最大值。
Optional reduce = Stream.of(1, 2, 3, 4, 5, 6).reduce(Integer::sum);
// 方法二,在方法一的基础上,增加初始值。常用于将一个 String 类型的 Stream 中的所有元素连接到一起并在最前面添加 value 后返回。
String reduce = Stream.of("Hello", " ", "World").reduce("Log:", (s1, s2) -> s1+ s2);
// 方法三,并行时,将 identity 参数与序列中的每一个元素进行 accumulator 转换,得到N个结果,然后使用 combiner 对N个结果进行汇总。
// 使用函数表示就是:(4+1) * (4+2) * (4+3) = 210;
int reduce = Stream.of(1, 2, 3).reduce(4, (v1, v2) -> v1 + v2, (v1, v2) -> v1 * v2);

reduce即使是并行情况下,也不会创建多个初始化对象,combiner接收的两个参数永远是同一个对象。

// array1 == array2 永远为True
Predicate predicate = t -> t.contains("a");
        ArrayList list = Stream.of("aa", "ab", "c", "ad").parallel().reduce(new ArrayList<>(),
                (array, s) -> {
                    if (predicate.test(s)) array.add(s);
                    return array;
                },
                (array1, array2) -> {
                    System.out.println(array1 == array2);
                    array1.addAll(array2);
                    return array1;
                });

toArray 转换成数组

有两种方法可以调用,分别是:

Integer[] array = Stream.of(1, 2, 3, 4, 5, 6).toArray(Integer[]::new);

collect 收集

collect含义与reduce有点相似,区别是reduce即使是并行情况下,也不会创建多个初始化对象,combiner接收的两个参数永远是同一个对象
有两种方法可以调用,分别是:

  • R collect(Supplier supplier, BiConsumer accumulator, BiConsumer combiner);
  • R collect(Collector collector);
// 方法一,和 reduce 的三个参数方法用法类似。
Predicate predicate = t -> t.contains("a");
ArrayList list = Stream.of("aa", "ab", "c", "ad").parallel().collect(ArrayList::new,
    (array, s) -> {
        if (predicate.test(s)) array.add(s);
    },
    ArrayList::addAll);
// 使用 Collectors 提供的 API
stream.collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(User::getName))), ArrayList::new)).forEach(System.out::println);

你可能感兴趣的:(Stream API)