乐趣区

关于后端:聊一聊Java中的Steam流-京东物流技术团队

1 引言

在咱们的日常编程工作中,对于汇合的制作和解决是必不可少的。当咱们须要对于汇合进行分组或查找的操作时,须要用迭代器对于汇合进行操作,而当咱们须要解决的数据量很大的时候,为了进步性能,就须要应用到并行处理,这样的解决形式是很简单的。流能够帮忙开发者节约贵重的工夫,让以上的事件变得轻松。

2 流简介

流到底是什么呢?简要的定义为“从反对数据处理操作的源生成的元素序列”,接下来对于这个定义进行简要剖析。

2.1 反对数据处理操作

流的数据处理操作和数据库的能够申明式的指定分组或查找等性能反对相似,和函数式编程的思维统一,如 filter、map、reduce、find、match、sort 等操作,这些流操作能够串行执行,也能够并行执行。

2.2 源

流会应用一个提供数据的源,能够通过三种形式来创建对象流,一种是由汇合对象创立流:

List<Integer> list = Arrays.asList(111,222,333);
Stream<Integer> stream = list.stream();

一种是由数组创立流:

IntStream stream = Arrays.stream(new int(){111,222,333});

一种是由静态方法 Stream.of() 创立流,底层还是 Arrays.stream():

Stream<Integer> stream = Stream.of(111, 222, 333);

Stream stream = Stream.of(111, 222, 333);

从有序汇合生成流时会保留原有的程序。由列表生成的流,其元素程序与列表统一。
还有两种非凡的流:

  • 空流:Stream.empty()
  • 有限流:Stream.genarate()

2.3 元素序列

流也能够和汇合一样拜访蕴含特定的元素类型的一组有序值,然而它们的次要目标不一样,汇合的次要目标是在于存储和拜访元素,流的次要目标在于表白计算。

3 流的思维

流式思维和生产中的流水线具备殊途同归之妙,很多流模型都会返回一个流,这些模型都只负责它所须要做的事件,并不需要分外的内存空间来存储解决的后果。这些流模型能够被链接起来造成一个大的流水线,咱们在这个过程中不关注两头步骤的数据被如何解决,只须要应用整个流水线解决后的后果。接下来的代码能够体现这种思维,代码中以商品为例,咱们要筛选出商品中体积大于 200 的前两个商品的名字。

首先是商品类的定义:

public class Goods {
    private final String Name;
    private final Integer Volume;

    public Goods(String name, Integer volume) {
        Name = name;
        Volume = volume;
    }
    public String getName() {return Name;}
    public Integer getVolume() {return Volume;}
}

接下来是商品汇合的定义:

List<Goods> goods = Arrays.asList(new Goods("土豆",10),
new Goods("冰箱",900),new Goods("办公椅",300));

接下来获取咱们想要的后果:

List<String> twofoods = goods.stream()// 获取流
    .filter(goods1 -> goods1.getVolume()>200)// 筛选商品体积大于 200 的
    .map(Goods::getName)// 获取商品名称
    .limit(2)// 筛选头两个商品
    .collect(Collectors.toList());// 将后果保留在 list 中 

这样看来,通过流来解决咱们的特定需要,是不是比应用汇合的迭代要不便很多呢?

4 流解决的个性

  • 不存储数据
  • 不会扭转数据源
  • 只可被应用一次

这里咱们应用一个测试类 StreamCharacteristic 来验证流解决的以上个性:

import org.springframework.util.Assert;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class StreamCharacteristic {public void test1(){List<Integer> list = Arrays.asList(1,2,2,5,6,9);
        list.stream().distinct();
        System.out.println(list.size());
    }
    public void test2(){List<String> list = Arrays.asList("wms", "KA", "5.0");
        Stream<String> stream = list.stream();
        stream.forEach(System.out::println);
        stream.forEach(System.out::println);
    }
}

test1() 中的后果为 6,只管咱们对于 list 对象所生成的 Stream 流做了去重操作 distinct(),然而不影响数据源 list。

test2() 中调用了两次 stream.forEach 办法来打印每一个单词,第二次调用时,抛出了一个“java.lang.IllegalStateException”异样:“stream has already been operated upon or closed”。这阐明流不存储数据,遍历完后这个流曾经被生产掉了,而且流不能够重复使用。

5 流操作与流的应用

将所有的流操作连接起来能够组合成一个管道,管道有两类操作:两头操作和终端操作。
StreamAPI 罕用的两头操作有:filter,map,limit,sorted,distinct。

StreamAPI 罕用的终端操作有:forEach,count,collect。

在应用流的时候,次要须要三个因素:一个用来执行查问的数据源,用来造成一条流的流水线的两头操作链,一个可能执行流水线并能生成后果的终端操作。

下图展现了流的整个操作流程:

[]()

6 总结

  • 流是从反对数据处理操作的源生成的元素序列
  • 流的思维相似于生产中的流水线
  • 流不存储数据,不扭转数据源,只能被扭转一次
  • 流的操作次要分为两头操作和终端操作两大类

作者:京东物流 王辰玮

起源:京东云开发者社区 自猿其说 Tech

退出移动版