转载请注明出处:https://zhuanlan.zhihu.com/p/20540202java
Stream做为Java8的新特性之一,他与Java IO包中的InputStream和OutputStream彻底不是一个概念。Java8中的Stream是对集合功能的一种加强,主要用于对集合对象进行各类很是便利高效的聚合和大批量数据的操做。结合Lambda表达式能够极大的提升开发效率和代码可读性。git
假设咱们须要把一个集合中的全部形状设置成红色,那么咱们能够这样写github
for (Shape shape : shapes){ shape.setColor(RED) }
若是使用Java8扩展后的集合框架则能够这样写:数据结构
shapes.foreach(s -> s.setColor(RED));
__第一种__写法咱们叫外部迭代,for-each调用shapes
的iterator()
依次遍历集合中的元素。这种外部迭代有一些问题:多线程
for循环是串行的,并且必须按照集合中元素的顺序依次进行;框架
集合框架没法对控制流进行优化,例如经过排序、并行、短路求值以及惰性求值改善性能。ide
上面这两个问题咱们会在后面的文章中逐步解答。函数
__第二种__写法咱们叫内部迭代,两段代码虽然看起来只是语法上的区别,但实际上他们内部的区别其实很是大。用户把对操做的控制权交还给类库,从而容许类库进行各类各样的优化(例如乱序执行、惰性求值和并行等等)。总的来讲,内部迭代使得外部迭代中不可能实现的优化成为可能。性能
外部迭代同时承担了作什么(把形状设为红色)和怎么作(获得Iterator实例而后依次遍历),而内部迭代只负责作什么,而把怎么作留给类库。这样代码会变得更加清晰,而集合类库则能够在内部进行各类优化。优化
Stream不是集合元素,它也不是数据结构、不能保存数据,它更像一个更高级的Interator
。Stream提供了强大的数据集合操做功能,并被深刻整合到现有的集合类和其它的JDK类型中。流的操做能够被组合成流水线(Pipeline)。拿前面的例子来讲,若是我只想把蓝色改为红色:
shapes.stream() .filter(s -> s.getColor() == BLUE) .forEach(s -> s.setColor(RED));
在Collection
上调用stream()
会生成该集合元素的流,接下来filter()
操做会产生只包含蓝色形状的流,最后,这些蓝色形状会被forEach
操做设为红色。
若是咱们想把蓝色的形状提取到新的List里,则能够:
List<Shape> blue = shapes.stream() .filter(s -> s.getColor() == BLUE) .collect(Collectors.toList());
collect()
操做会把其接收的元素汇集到一块儿(这里是List),collect()
方法的参数则被用来指定如何进行汇集操做。在这里咱们使用toList()
以把元素输出到List中。
若是每一个形状都被保存在Box
里,而后咱们想知道哪一个盒子至少包含一个蓝色形状,咱们能够这么写:
Set<Box> hasBlueShape = shapes.stream() .filter(s -> s.getColor() == BLUE) .map(s -> s.getContainingBox()) .collect(Collectors.toSet());
map()
操做经过映射函数(这里的映射函数接收一个形状,而后返回包含它的盒子)对输入流里面的元素进行依次转换,而后产生新流。
若是咱们须要获得蓝色物体的总重量,咱们能够这样表达:
int sum = shapes.stream() .filter(s -> s.getColor() == BLUE) .mapToInt(s -> s.getWeight()) .sum();
流(Stream)和集合(Collection)的区别:
Collection主要用来对元素进行管理和访问;
Stream并不支持对其元素进行直接操做和直接访问,而只支持经过声明式操做在其之上进行运算后获得结果;
Stream不存储值
对Stream的操做会产生一个结果,可是Stream并不会改变数据源;
大多数Stream的操做(filter,map,sort等)都是以惰性的方式实现的。这使得咱们可使用一次遍历完成整个流水线操做,并能够用短路操做提供更高效的实现。
filter()
和map()
这样的操做既能够被急性求值(以filter()
为例,急性求值须要在方法返回前完成对全部元素的过滤),也能够被惰性求值(用Stream
表明过滤结果,当且仅当须要时才进行过滤操做)在实际中进行惰性运算能够带来不少好处。好比说,若是咱们进行惰性过滤,咱们就能够把过滤和流水线里的其它操做混合在一块儿,从而不须要对数据进行多遍遍历。相相似的,若是咱们在一个大型集合里搜索第一个知足某个条件的元素,咱们能够在找到后直接中止,而不是继续处理整个集合。(这一点对无限数据源是很重要,惰性求值对于有限数据源起到的是优化做用,但对无限数据源起到的是决定做用,没有惰性求值,对无限数据源的操做将没法终止)
对于filter()
和map()
这样的操做,咱们很天然的会把它当成是惰性求值操做,不过它们是否真的是惰性取决于它们的具体实现。另外,像sum()
这样生成值的操做和forEach()
这样产生反作用的操做都是__自然急性求值__,由于它们必需要产生具体的结果。
咱们拿下面这段代码举例:
int sum = shapes.stream() .filter(s -> s.getColor() == BLUE) .mapToInt(s -> s.getWeight()) .sum();
这里的filter()
和map()
都是惰性的,这就意味着在调用sum()
以前不会从数据源中提取任何元素。在sum()
操做以后才会把filter()
、map()
和sum()
放在对数据源一次遍历中。这样能够大大减小维持中间结果所带来的开销。
<!--####6.流水线(Pipeline)的并行操做
流水线能够是串行的也能够是并行的,串行和并行是流的属性。默认状况下数据源返回的都是串行流,可是咱们能够经过parallel()
将串行流转换为并行流,就像下面这样:
int sum = shapes.parallelStream() .filter(s -> s.getColor = BLUE) .mapToInt(s -> s.getWeight()) .sum();
那么,串行流和并行流有什么区别呢?
流的数据源多是一个可变集合,若是当咱们在遍历流时数据源被改变了,那么就会产生干扰。因此在进行流操做的时候,数据源应该保持不变。若是在单线程模型下,咱们只须要保证lambda表达式不修改流的数据源就OK了;但若是是多线程环境,lambda在执行时可能会同时运行在多个线程上-->
前面长篇大论的介绍概念实在太枯燥,为了方便你们理解咱们用Streams API来实现一个具体的业务场景。
假设咱们有一个房源库项目,这个房源库中有一系列的小区,每一个小区都有小区名和房源列表,每套房子又有价格、面积等属性。如今咱们须要筛选出含有100平米以上房源的小区,并按照小区名排序。
咱们先来看看不用Streams API如何实现:
List<Community> result = new ArrayList<>(); for (Community community : communities) { for (House house : community.houses) { if (house.area > 100) { result.add(community); break; } } } Collections.sort(result, new Comparator<Community>() { @Override public int compare(Community c1, Community c2) { return c1.name.compareTo(c2.name); } }); return result;
若是使用Streams API:
return communities.stream() .filter(c -> c.houses.stream().anyMatch(h -> h.area>100)) .sorted(Comparator.comparing(c -> c.name)) .collect(Collectors.toList());
若是你们喜欢这一系列的文章,欢迎关注个人知乎专栏、GitHub、简书博客。