文档章节

java8新特性(3)-Stream举例

十二缸帕萨特
 十二缸帕萨特
发布于 2016/04/18 17:26
字数 2367
阅读 174
收藏 7
点赞 1
评论 0

流(Stream) 可以定义为从一个源读取而来的支持聚集操作的一系列元素。这里说的源指的是集合或者数组,她们可以为源提供数据。流(Stream) 中数据的顺序和源保持一致。聚集操作或者批量操作允许我们很容易对流(Stream) 中的元素进行常见操作。

我将从下面几个方面讨论 Java 8 中的 流(Stream),如无特殊说明,下文中的流指的都是Stream。

在继续下文之前,有必要事先说明一下, Java 8 中的多数流操作只返回流对象。这有助于写出链式流操作,我们管这个叫管线。在这篇文章中我会多次使用这个术语,所以务必记住了哦。

流 vs. 集合

相信大家都在优酷上看过在线视频,当你开始观看视频时,文件的一小部分会首先下载到你的电脑中然后开始播放。在开始播放视频之前你并不需要把整个视频下载下来。我将尝试把这个概念与流关联起来。

在基本概念层面,集合与流的区别在于其中的元素是如何产生的。集合是存在于内存中的数据结构,其中保存了所有的元素对象,这些元素对象在加入到集合 中之前,必须已经构建好。流本质上不是数据结构,其中的元素对象是按需产生的。这带来了显著的好处,用户只能从流中取到他们真正需要的元素,这些元素也只 在用户真正需要的时候被生产出来。这看起来是一种生产者-消费者的关系。

在 Java 中,java.util.stream.Stream代表流,在流上可以执行各种流操作。流操作分为中间操作和末端操作。末端操作会返回某一类型的结果,而中间操作会返回流对象,所以你可以把多个中间操作串成一行。流基于源而生成,比如List和Set都可以作为源(Map不行)。流操作可以串行执行,也可以并行执行。

基于上述观点,我们可以总结出来流的基本特征:

  • 不是数据结构

  • 为 lambda 表达式而设计

  • 不支持基于索引的访问方式

  • 可以很容易的被转换为集合

  • 支持延迟访问

  • 可以并行执行

各种构建流对象的方式

下面是最常见的从集合产生流对象的方式  
使用Stream.of()

 public class StreamBuilders {
     public static void main(String[] args) {
         Stream<String> stream = Stream.of("a", "b", "c", "d", "e", "f", "g");
         stream.forEach(p -> System.out.println(p));
     }
 }

使用Collection.stream()

public class StreamBuilders {
     public static void main(String[] args) {
         List<String> strings = Arrays.asList("a", "b", "c", "d", "e", "f", "g");
         Stream<String> stream = strings.stream();
         stream.forEach(p -> System.out.println(p));
     }
 }

使用generate()

public class StreamBuilders {
     public static void main(String[] args) {
         Stream<Double> stream = Stream.generate(() -> {
             return Math.random();
         }).limit(5);
         stream.forEach(p -> System.out.println(p));
     }
 }

使用iterate()

 public class StreamBuilders {
     public static void main(String[] args) {
         Stream<Integer> stream = Stream.iterate(10, i -> i + 10).limit(5);
         stream.forEach(i -> System.out.println(i));
     }
 }

使用CharSequence.chars()

 public class StreamBuilders {
     public static void main(String[] args) {
         IntStream stream = "ABCDEFG_abcdefg".chars();
         stream.forEach(p -> System.out.println(p));
     }
 }

还有一些其它的方式也可以产生流对象,比如使用java.util.stream.Stream.Builder<T>或者使用中间操作。我们会在后续的文章中学习到。

转换流为集合

其实不应该说是转换,因为这里并没有发生流对象转换为其它对象的过程,更应该说是把流中的元素放到其它数据结构中,比如集合或者数组。但是如果老是这么描述,有显得太过罗嗦,所以大家务必理解下面我提到的转换。  
使用stream.collect(Collectors.toList())把Stream转换为List

 public class StreamBuilders {
     public static void main(String[] args) {
         List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
         Stream<Integer> stream = list.stream();
         List<Integer> oddNumbersList = stream.filter(i -> i % 2 != 0).collect(Collectors.toList());
         System.out.print(oddNumbersList);
     }
 }

使用stream.collect(Collectors.toSet())把Stream转换为Set

 public class StreamBuilders {
     public static void main(String[] args) {
         List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
         Stream<Integer> stream = list.stream();
         List<Integer> oddNumbersSet = stream.filter(i -> i % 2 != 0).collect(Collectors.toSet());
         System.out.print(oddNumbersSet);
     }
 }

使用stream.toArray(EntryType[]::new)把Stream转换为数组

 public class StreamBuilders {
     public static void main(String[] args) {
         List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
         Stream<Integer> stream = list.stream();
         Integer[] oddNumbersArray = stream.filter(i -> i % 2 != 0).toArray(Integer[]::new);
         System.out.print(oddNumbersArray);
     }
 }

还有许多方法可以把Stream转换为Map或其它数据类型。你只需要去浏览下java.util.stream.Collectors,把它们记住即可。

核心的流操作

你可以在流对象上执行非常多有用的函数。 我并不打算把他们都讲到,但是我计划把最最重要的那些讲一下,这些都是你必须第一时间掌握的。

在继续之前,我们先构建一个字符串集合,下面的例子都会基于这个集合进行操作。

List<String> provinceNames = Arrays.asList("湖南", "湖北", "河南", "河北", "广东", "广西", "北京", "南京");

这些核心的流操作方法,可以划分为两类:

中间操作

中间操作返回流对象,所以你可以把多个中间操作串成一行。  
filter()

filter 方法接受一个断言对象,用来过滤流中的元素。

 provinceNames.stream().filter((p) -> p.startsWith("湖")).forEach(p -> System.out.print(p + ", "));
 
 // 输出 : 
 // 湖南, 湖北,

map()

这个中间操作使用给定的函数把流中的每个元素转换为另一个对象。下面的例子把每个字符串转化为另一个字符串(末尾多了一个人字)。但是你也可以把每个对象转换为其它类型的对象。

provinceNames.stream().map(p -> p + "人, ").forEach(p -> System.out.print(p));
 
 // 输出 : 
 // 湖南人, 湖北人, 河南人, 河北人, 广东人, 广西人, 北京人, 南京人,

sorted()

sorted 会对流中的元素进行排序,默认按自然序进行排序,除非你指定一个自定义的比较器(Comparator) 。

 provinceNames.stream().sorted().map(p -> p + "人, ").forEach(p -> System.out.print(p));
 
 // 输出 : 
 // 北京人, 南京人, 广东人, 广西人, 河北人, 河南人, 湖北人, 湖南人,

需要注意的是sorted仅仅对流中的元素进行排序,而不会影响后面的集合,集合中的元素排序保持不变。

末端操作

末端操作返回某一类型的结果,而不是流对象。  
forEach()

该方法帮助迭代流中的元素并在元素上执行一些操作。这些操作可以是 lambda 表达式或者方法引用。


collect()

collect() 方法用来从流中抽取元素然后保存到一个集合或者数组中。

 List<String> provinceNamesOrdered = provinceNames.stream().sorted().collect(Collectors.toList());
 System.out.print(provinceNamesOrdered);
 
 // 输出 : 
 // [北京, 南京, 广东, 广西, 河北, 河南, 湖北, 湖南]

match()

各种匹配操作用来检查流中的元素是否指定断言条件。所有的匹配操作都是末端操作,它们返回布尔结果。

 boolean matchedResult = provinceNames.stream().anyMatch((s) -> s.startsWith("湖"));
 System.out.println(matchedResult);
 
 matchedResult = provinceNames.stream().allMatch((s) -> s.startsWith("湖"));
 System.out.println(matchedResult);
 
 matchedResult = provinceNames.stream().noneMatch((s) -> s.startsWith("湖"));
 System.out.println(matchedResult);
 
 // 输出 : 
 // true
 // false
 // false

count()

count 末端操作返回流中元素个数。

 long totalMatched = provinceNames.stream().count(); 
 System.out.println(totalMatched); 
  // 输出 : 8

reduce()

该末端操作使用给定的函数对流中的元素进行归约。它是这样一个过程:每次迭代,将上一次的迭代结果(第一次时为 identity 的元素,如没有  identity 则为流中的第一个元素)与下一个元素一同执行一个二元函数。在 reduce 操作中,identity  是可选的,如果使用,则作为第一次迭代的第一个元素使用。归约的结果保存在Optional中。

Optional<String> reduced = provinceNames.stream().reduce((s1, s2) -> s1 + "#" + s2); 
reduced.ifPresent(System.out::println);

短路操作

流操作通常会在流中满足某一断言的所有元素上进行操作,有时我们希望在迭代过程中遇到匹配的元素时就终止操作,在外部迭代中,你需要写 if-else 代码块,在内部迭代中,有现成的方法可以使用,下面是两个这样的方法的示例:  
anyMatch()

该操作只要遇到满足断言条件的元素就会返回true,在此之后就不再处理任何其它的元素了。

boolean matched = provinceNames.stream().anyMatch((s) -> s.startsWith("河")); 
System.out.println(matched); 
 // 输出 : true

findFirst()

该操作会返回流中的第一个元素,然后就不再处理其它元素了。

String firstMatchedName = provinceNames.stream().filter((s) -> s.startsWith("广")).findFirst().get(); 
 System.out.println(firstMatchedName);

并行处理

有了 Java 7 中的 Fork/Join 框架,我们有了高效的实现并行操作的机制,但是使用这个框架非常复杂,如果实现不当,那就会引入无数的令人费解的多线程 bug,甚至可能使得应用程序崩溃。有了内部迭代,我们一样可以实现并行操作。

要启用并行性,你只需创建一个并行流,而不是串行流。这会让你觉得非常惊讶,因为真是太简单了。在上面所有的流操作例子中,任何时候你希望在多核中并行执行你的任务,你只需要改调用parallelStream()方法而不是stream()方法。

 public class StreamBuilders {
     public static void main(String[] args) {
         List<Integer> list = new ArrayList<Integer>();
         for (int i = 1; i < 10; i++) {
             list.add(i);
         }
         // 在这里创建并行流
         Stream<Integer> stream = list.parallelStream();
         Integer[] evenNumbersArr = stream.filter(i -> i % 2 == 0).toArray(Integer[]::new);
         System.out.print(evenNumbersArr);
     }
 }


© 著作权归作者所有

共有 人打赏支持
十二缸帕萨特
粉丝 17
博文 62
码字总数 29186
作品 0
海淀
高级程序员
Java8解决了什么?

在学习面向对象时,许多人都会用Java来举例子,但是其实Java并非纯正的面向对象语言,最明显的就是:int,double等基本类型不是对象。 自从java8出来过后,引入了流,函数式编程,就更不是在向...

MageekChiu ⋅ 2017/08/02 ⋅ 0

Java8新特性系列(Stream)

上期我们分析了Java8中的引用,本期我们将分析Java8中的另一个重要的新特性:流Stream。 Stream是什么? 在Java8源代码中,是这么定义Stream的: A sequence of elements supporting sequen...

码上论剑 ⋅ 02/03 ⋅ 0

Java8 学习笔记

先来一个概览,上图是我整理的Java8中的新特性,总的来看,大致上可以分成这么几个大块。 函数式接口 所谓的函数式接口就是只有一个抽象方法的接口,注意这里说的是抽象方法,因为Java8中加入...

xrzs ⋅ 2013/05/14 ⋅ 0

Java 8新特性(二)

集合类的批处理: Java8除了Lambda表达式外还提供了另一个重要的特性,即集合的批处理操作,集合类的批处理操作API的目的是实现集合类的“内部迭代”,并期望充分利用现代多核CPU进行并行计算...

casoc ⋅ 2015/06/02 ⋅ 0

Java8-Stream-创建流

友情提示:如果要看懂Stream,得先把Lambda先搞明白,可以去Java8-Lambda表达式瞧一瞧 什么是Stream Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行...

特拉仔 ⋅ 02/24 ⋅ 0

Java系列 – 用Java8新特性进行Java开发太爽了(续)

本人博客文章网址:https://www.peretang.com/using-java8s-new-features-to-coding-is-awesome-2/ 前言 上周, 我们谈论了关于Java8的新特性有那些, 什么是函数式编程, 什么是Lambda表达式, 这...

PereTang ⋅ 2017/07/18 ⋅ 0

Java8-初识Lambda

廉颇老矣,尚能饭否 Java,这位已经20多岁的编程语言,称得上是编程语言界的老大哥了。他曾经攻城略地,碾压各路编程语言小弟,风光无限,不可一世。现在,也是家大业大,江湖地位,很难撼动...

Jackie_Zheng ⋅ 2017/09/17 ⋅ 0

【java8】java新特性(一)——全局观

一、前言 年前的时候 ,我一个师姐出去工作,被鄙视了。说写的代码太垃圾。当时我也没有在意,回头想想自己,本以为自己写的代码天衣无缝,无可挑剔。但是自从自己遇到了Java8 后,我的世界观...

kisscatforever ⋅ 03/15 ⋅ 0

Axon 3 发布,Java 的 CQRS 框架

Axon 3 最初的想法始于两年前,第一次commit大概在15个月前,6个月前发布了第一个里程碑,然后最终3.0 GA版本在此呈现。这真的是依次让人自豪的旅程。 Axon 3是对Axon内部工作机制的一次大修...

囚兔 ⋅ 2017/01/05 ⋅ 2

Java新特性性能

java 8 的 "Stream“ 新特性,效率如何?该何时使用? 简单测试,求1000000个数中的偶数, 普通for循环加if判断执行大概 16.564ms java8 的 stream 写法执行大概88.142ms。...

BugRookieH ⋅ 2017/06/01 ⋅ 2

没有更多内容

加载失败,请刷新页面

加载更多

下一页

AppDelegate 设置Root相关

self.window = UIWindow.init(frame: UIScreen.main.bounds) self.window?.backgroundColor = UIColor.white self.window?.makeKeyAndVisible() self.window?.rootViewController = RootTabB......

west_zll ⋅ 19分钟前 ⋅ 0

Java并发系列5--倒计时器CountDownLatch

今天讲一个倒计时器工具,叫CountDownLatch。需要这个工具的场景大概有:当所有的小任务都完成之后,再启动大任务。 先看代码: public class CountDownLatchDemo {static final CountDow...

大大枣 ⋅ 21分钟前 ⋅ 0

SpreadJS使用进阶指南 - 使用 NPM 管理你的项目

前言 SpreadJS作为一款性能出众的纯前端电子表格控件,自2015年发布以来,已经被广泛应用于各领域“在线Excel”数据管理项目中。NPM,作为管理Node.js库最有力的手段,解决了很多NodeJS代码部...

葡萄城控件技术团队 ⋅ 22分钟前 ⋅ 0

Mac下IntelliJ IDEA快捷键大全

https://blog.csdn.net/lisongjia123/article/details/54949364

细节探索者 ⋅ 24分钟前 ⋅ 0

建造者模式

1、工厂模式中创建的对象大都是简单的对象 复杂的产品类并且拥有不同的属性特点的管理就需要用到建造者模式 2、建造者模式: 将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以...

职业搬砖20年 ⋅ 26分钟前 ⋅ 0

Mysql数据库开发 怎么优化SQL语句?

 1) 现场抓出慢查询语句 show full processlist;   2) 配置参数:   slow_query_log_file = ON 慢查询开启开关   long_query_time =2 记录大于2秒的sql语句   log_queries_not_usi...

老男孩Linux培训 ⋅ 26分钟前 ⋅ 0

Laravel 安装执行php artisan migrate 出现字段过长错误

最近在自己研究Laravel Laravel版本:5.6 PHP版本:7.1.9 Mysql版本:5.7.19 Apache版本:2.4.27 系统版本:windows10 首先要保证电脑安装了composer,和node.js 执行命令 composer global ...

Marhal ⋅ 31分钟前 ⋅ 0

ELK6.0日志从收集到处理完整版教程(二)

ELK简介 Elasticsearch 开源分布式搜索引擎,它的特点有:分布式,零配置,自动发现,索引自动分片,索引副本机制,restful风格接口,多数据源,自动搜索负载等。也可以认为ElasticSearch是一...

bz_z ⋅ 34分钟前 ⋅ 0

Spark项目之电商用户行为分析大数据平台之(七)数据调研--基本数据结构介绍

目录 一、user_visit_action(Hive表) 1.1 表的结构 1.2 表的说明 二、user_info(Hive表) 2.1 表的结构 2.2 表的说明 三、task(MySQL表) 3.1 表的结构 3.2 表的说明 四、工作流程...

xiaomin0322 ⋅ 39分钟前 ⋅ 0

评分卡模型剖析之一(woe、IV、ROC、信息熵)

信用评分卡模型在国外是一种成熟的预测方法,尤其在信用风险评估以及金融风险控制领域更是得到了比较广泛的使用,其原理是将模型变量WOE编码方式离散化之后运用logistic回归模型进行的一种二...

火力全開 ⋅ 40分钟前 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部