Java Stream

Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。
Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。
Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。
元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。

Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。

Stream 的另外一大特点是,数据源本身可以是无限的。

1. Stream的定义

Stream(流)是一个来自数据源的元素队列并支持聚合操作

  • 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
  • 数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
  • 聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。

和以前的Collection操作不同, Stream操作还有两个基础的特征:

  • Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
  • 内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。

2. 流的构成

当我们使用流的时候,通常包括三个步骤:

获取一个数据源(source)→ 数据转换→执行操作获取想要的结果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道

JAVA_STRUCT如何解析 java octet-stream解析_stream

基本语法:

JAVA_STRUCT如何解析 java octet-stream解析_java_02

3. 流的生成

3.1 集合生成流

在 Java 8 中, 集合接口有两个方法来生成流:

stream() // 为集合创建串行流。
parallelStream() // 为集合创建并行流。

例如:

List<String> list = new ArrayList<>();
Stream<String> stream = list.stream();
Stream<String> stringStream = list.parallelStream();

3.2 数组生成流

通过 Arrays.stram(),或通过Stream.of() 例如:

Stream<String> stream1 = Arrays.stream(new String[10]);
Stream<Integer> stream2 = Stream.of(1, 2, 3);

3.3 生成无限流

  1. 通过Stream.iterate()生成无限流
Stream<Integer> iterate = Stream.iterate(0, (x) -> x + 2);
iterate.limit(10).forEach(System.out::println);
  1. 通过Stream.generate()
Stream<Double> generate = Stream.generate(() -> Math.random());
generate.forEach(System.out::println);

4. 流的转换

流的转换有4种方式:

  • distinct
  • filter
  • map
  • flatMap

1. distinct

对于Stream中包含的元素进行去重操作(去重逻辑依赖元素的equals方法),新生成的Stream中没有重复的元素;

JAVA_STRUCT如何解析 java octet-stream解析_JAVA_STRUCT如何解析_03

// 生成一个 0-10 的15个随机数
public static List<Integer> generateArray() {
    return Stream.generate(() -> (int)(Math.random() * 10)).limit(15).collect(Collectors.toList());
}

// 去重
public void TestDistinct() {
    
    List<Integer> collect = generateArray();
    System.out.println(collect);
    // 去重
    List<Integer> collect1 = collect.stream().distinct().collect(Collectors.toList());
    System.out.println(collect1);
}

2.filter

对于Stream中包含的元素使用给定的过滤函数进行过滤操作,新生成的Stream只包含符合条件的元素

JAVA_STRUCT如何解析 java octet-stream解析_System_04

// 得到数组中的偶数
public void TestFilter() {

    List<Integer> data = generateArray();
    System.out.println(data);
    // 去重
    List<Integer> collect = data.stream().filter( x -> x % 2 == 0).collect(Collectors.toList());
    System.out.println(collect);
}

3. map

对于Stream中包含的元素使用给定的转换函数进行转换操作,新生成的Stream只包含转换生成的元素。

JAVA_STRUCT如何解析 java octet-stream解析_stream_05

// 计算数组中的每个元素的平方
public void TestMap() {
    List<Integer> data = generateArray();
    System.out.println(data);
    List<Integer> collect = data.stream().map(x -> x * x).collect(Collectors.toList());
    System.out.println(collect);
}

4. flatMap

对于Stream中包含的元素使用给定的转换函数进行转换操作,新生成的Stream只包含转换生成的元素。

JAVA_STRUCT如何解析 java octet-stream解析_java_06

5. peek

生成一个包含原Stream的所有元素的新Stream,同时会提供一个消费函数(Consumer实例),新Stream每个元素被消费的时候都会执行给定的消费函数

6. limit

对一个Stream进行截断操作,获取其前N个元素,如果原Stream中包含的元素个数小于N,那就获取其所有的元素;

JAVA_STRUCT如何解析 java octet-stream解析_stream_07

7. skip

返回一个丢弃原Stream的前N个元素后剩下元素组成的新Stream,如果原Stream中包含的元素个数小于N,那么返回空Stream;

JAVA_STRUCT如何解析 java octet-stream解析_JAVA_STRUCT如何解析_08

public void TestSkip() {
    List<Integer> data = generateArray();
    System.out.println(data);
    List<Integer> collect = data.stream().skip(5).collect(Collectors.toList());
    System.out.println(collect);
}

8. sorted

sorted方法将对原Stream进行排序,返回一个有序列的新Stream。sorterd有两种变体sorted(),sorted(Comparator),前者将默认使用Comparator进行排序,而后者接受一个自定义排序规则函数(Comparator),可按照意愿排序。

public void TestSorted() {
    List<Integer> data = generateArray();
    System.out.println(data);
    // 默认排序
    List<Integer> collect = data.stream().sorted().collect(Collectors.toList());
    System.out.println(collect);

    // 自定义排序:逆序排序
    List<Integer> collect1 = data.stream().sorted((n1, n2) -> n2 - n1).collect(Collectors.toList());
    System.out.println(collect1);
}

5. 流的聚合(Reduce)

汇聚操作(也称为折叠)接受一个元素序列为输入,反复使用某个合并操作,把序列中的元素合并成一个汇总的结果。比如查找一个数字列表的总和或者最大值,或者把这些数字累积成一个List对象。Stream接口有一些通用的汇聚操作,比如reduce()collect();也有一些特定用途的汇聚操作,比如sum(),max()count()

1. 可变汇聚

可变汇聚对应的只有一个方法:collect,正如其名字显示的,它可以把Stream中的要有元素收集到一个结果容器中(比如Collection)

int n=1;
List<Integer> data = Stream.generate(() -> n++).limit(5).collect(Collectors.toList());
System.out.println(data);
// map
Map<Integer, Integer> dmap = data.stream().collect(Collectors.toMap(x->x, x->x*x));
System.out.println(dmap);
// set
Set<Integer> dset = data.stream().collect(Collectors.toSet());
System.out.println(dset);

Reduce

这个方法的主要作用是把 Stream 元素组合起来。它提供一个起始值(种子),然后依照运算规则(BinaryOperator),和前面 Stream 的第一个、第二个、第 n 个元素组合。从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce.

例如:

Integer sum = integers.reduce(0, (a, b) -> a+b);
Integer sum = integers.reduce(0, Integer::sum);

示例:

// 求数组的和
public void TestReduce() {
    List<Integer> data = Arrays.asList(1,2,3);
    System.out.println(data);
    Integer sum = data.stream().reduce((s, y) -> s + y).get();
    System.out.println("sum=" + sum);

    // 第二种
    Integer sum1 = data.stream().reduce(0, (s, y) -> s + y);
    System.out.println("sum1=" + sum1);
}

// 求数组的乘积
public void TestReduce() {
    List<Integer> data = Arrays.asList(1,2,3);
    System.out.println(data);
    Integer multi = data.stream().reduce((s, y) -> s * y).get();
    System.out.println("multi=" + multi);

    // 第二种
    Integer multi1 = data.stream().reduce(1, (s, y) -> s * y);
    System.out.println("multi=" + multi1);
}

其它汇聚

  • forEach:替代for循环
  • count:计算个数
  • allMatch:是不是Stream中的所有元素都满足给定的匹配条件
  • anyMatch:Stream中是否存在任何一个元素满足匹配条件
  • findFirst: 返回Stream中的第一个元素,如果Stream为空,返回空Optional
  • noneMatch:是不是Stream中的所有元素都不满足给定的匹配条件
  • maxmin:使用给定的比较器(Operator),返回Stream中的最大|最小值

2. 统计

一些产生统计结果的收集器也非常有用。它们主要用于int、double、long等基本类型上,它们可以用来产生类似如下的统计结果。

常用方法:

  • getAverage()
  • getCount()
  • getMax()
  • getMin()
  • getSum()
public void TestStatic() {
        List<Integer> data = generateArray();
        System.out.println(data);
        IntSummaryStatistics statistics = data.stream().mapToInt(x -> x).summaryStatistics();
        double average = statistics.getAverage(); // 平局值
        long count = statistics.getCount(); // 计数
        int max = statistics.getMax(); // 最大值
        int min = statistics.getMin(); // 最小值
        long sum = statistics.getSum(); // 求和

    }

3. 分组

使用Collectors.groupingBy()生成的收集器,对元素做group操作时用到。

public void TestGroup() {
    List<Integer> data = generateArray();
    System.out.println(data);

    // 按奇偶分组
    Map<Boolean, List<Integer>> collect = data.stream().collect(Collectors.groupingBy(x -> x % 2 == 0, Collectors.toList()));
    System.out.println(collect);

    //按奇偶分组,并计数
    Map<Boolean, Long> collect1 = data.stream().collect(Collectors.groupingBy(x -> x % 2 == 0, Collectors.counting()));
    System.out.println(collect1);

    //按奇偶分组,并求和
    Map<Boolean, Integer> collect2 = data.stream().collect(Collectors.groupingBy(x -> x % 2 == 0, Collectors.summingInt(x -> x)));
    System.out.println(collect2);

    // 分组求平局值
    Map<Boolean, Integer> collect3 = data.stream().collect(Collectors.groupingBy(x -> x % 2 == 0, Collectors.summingInt(x -> x)));
    System.out.println(collect3);
}