文档章节

学习 Java8 函数式编程 (三)

cnbo
 cnbo
发布于 2017/08/26 10:03
字数 2090
阅读 49
收藏 1
点赞 0
评论 0

扯淡


在准备学 Java8 之前,我以为不会很难。所以,我就决定一边学 Java8,一边写博客。当我准备写这篇博客的时候,我发现两件都不容易。如果不是我亲身体验,我也没法知道一篇博客的背后,作者得付出多少时间和精力。这会让我在读每一篇博客时,保持对作者的敬畏之心。

本篇博客会简单介绍 Stream,以及认识是用 Stream 的 API。当我不知道这篇博客该怎么写的时候,我想试着从 Stream 的源码去解读。然后,我就傻逼似的,屁颠屁颠地去看 Stream 源码,结果可想而知,我抱着头顶上的一堆星星去床上思考人生了。Stream 的功能非常强大,给我们这些普通的程序员带来了极大的方便。这必然意味着,Java 的布道师们会在底层实现一堆逻辑非常复杂的代码。所以,学习一门新技术的时候,我们应该先知道这门技术是什么,可以干什么,然后熟练掌握,最后深究它背后的实现原理。

认识 Stream


第一眼看见 Stream 这个词的时候,会误认为和 Java 的 I/O 流有着不可描述的关系。其实,它们俩就像潘长江和曾志伟并不是兄弟一样,毫无关系。

其实和 Stream 有关系的是 Java 中的容器(我们经常使用的 List 和 Set 等集合)。在 Java8 之前,我们使用的是增强 for 语法糖和容器自身的 Iterator 来遍历数据。我们先来看下清单 1 中的代码,使用增强 for 和 Iterator 来遍历集合。

清单 1

List<String> strList = 
    Arrays.asList("a", "b", "c", "d");

//使用增强 for 遍历集合
for (String str : strList) {
    System.out.println(str);
}

//使用 Iterator 遍历集合
Iterator iterator = strList.iterator();
while (iterator.hasNext()) {
    String str = iterator.next();
    System.out.println(str);
}

清单 1 中的代码使用了增强 for 语法糖和 Iterator 来遍历一个集合。其实使用增强 for 和 Iterator 是一回事。因为增强 for 是 Java 为简化我们使用 Iterator 而提供的一个语法糖,它背后的实现依然是 Iterator。大家可能会发现,无论我们是使用增强 for 还是 Iteraotr 遍历集合,都需要将集合中的元素一个个的取出来。这种将元素从集合中取出来的迭代方式叫外部迭代

现在,我们来看下清单 2 中的代码,使用 Java8 中的 Stream 来遍历集合。

清单 2

List<String> strList = 
    Arrays.asList("a", "b", "c",  "d");
strList.stream()
    .forEach(str -> System.out.println(str));

当大家读完清单 2 中的代码后,是不是似乎有点知道 Stream 是干啥的了。通过请单 2 中的代码,我们只能看出 Stream 有遍历的集合功能,而且写法更加简单,代码的可读性也增强了。使用 Stream 遍历集合的方式叫内部迭代

Java8 的布道师们利用了面向对象的思想,对容器的存储数据和遍历数据进行了解耦。从 Java8 开始,在绝大多数情况下,我们都会使用 Stream 来遍历集合,容器只用来存储数据,无需关心遍历。

按我的理解,Stream 就是一种新的迭代方式,它以一种更加简单的方式对数据进行处理,让代码简洁易懂。在接下来的内容中,大家会发现使用 Stream 处理数据的代码,我们能够清晰读出代码的意图,而且代码中没有多余的临时变量和命令式代码。同时,使用 Stream 操作数据,并不会改变数据源(可以是容器,可以是数组,也可以是其他类型的数据源)。在多核时代,我们可以使用 Stream 写出高效以及线程安全的代码。但这不意味着我们需要写线程相关的代码,因为 Stream 在底层已经帮我们实现了。

在接下来使用 Stream 的过程中,我们只需要在 Stream 的每个函数中传入一个函数,传入的函数只是用来告诉 Stream 需要做什么,具体该如何做,并不需要我们关心。

使用 Stream API


准备三个类

在使用 Stream API 之前,我们先准备三个类 Artist (创作音乐的个人或团队),Track (专辑中的一些曲目),Album (专辑,由若干曲目组成)。接下来的内容,都会围绕这三个类展开。这三个类的具体定义如清单 3, 4, 5 所示。

清单 3

public class Artist {
    
    //艺术家的名字(例如 “纵贯线乐队”)
    private String name;
    
    //乐队成员(例如 “周华健"),该字段可为空
    private String members;
    
    //乐队来自哪里(例如 “台湾”)
    private String origin;
    
    //省略 set 和 get 方法
             
}

清单 4

public class Track {
    //曲目名称
    private String name;
    
    //省略 set 和 get 方法

}

清单 5

public class Album {
    
    //专辑名
    private String name;
    
    //专辑上所有曲目的列表
    private List<Track> tracks;
    
    //参与创作本专辑的艺术家列表
    private List<Artist> musicians;
    
    //省略 set 和 get 方法
        
}

案例描述

问题是,找出某张专辑上所有乐队的国籍。艺术家列表里既有个人,也有乐队,其中乐队名以 The 开头。

该问题可分解为如下几个步骤:

  • 找出专辑上的所有表演者
  • 分辨出哪些表演者是乐队
  • 找出每个乐队的国籍
  • 将找出的国籍放入一个集合

使用命令式代码实现

我们先是使用命令式风格的代码实现上述案例,代码如清单 6 所示。

清单 6

//将专辑中的艺术家为乐队的单独放入一个集合 bankList
List<Artist> artistList = album.getMusicians();
List<Artist> bankList = new ArrayList<>();
for (Artist artist : artistList) {
    if (artist.getName().startsWith("The")) {
        bankList.add(artist);
    }
}
        
//找出bankList中每个乐队的国籍,并将国籍放入originList
List<String> originList = new ArrayList<>();
for (Artist artist : bankList) {
    originList.add(artist.getOrigin());
}            

清单 6 的代码存在如下问题:

  • 存在多余的临时变量
  • 样板式代码掩盖了关键代码,代码的可读性很低

使用 Stream API 实现

我们现在使用 Stream API 来重写清单 6 中的代码,如清单 7 所示。

清单 7

Set<String> originList =
    album.getMusicians()
    .stream()
    .filter(artist -> artist.getName().startsWith("The"))
    .map(artist -> artist.getOrigin())
    .collect(toSet());

当大家看到清单 7 的代码时候,有没有很惊喜?反正我是为之跳跃。我先来解读下这段代码。其实,当大家熟悉 Stream 的 API 后,一眼就能看出这段代码的意图,它简直就是将问题的每一个小步骤描述了一遍,没有一点拖泥带水。首先通过 getMusicians 获取 album (专辑) 中的艺术家列表;然后使用艺术家列表构建一个 Stream,这里要说的是,所有 Collection 的子类都可以使用 stream 方法来构建一个 Stream,因为 Java8 允许接口中有 default 方法;接着调用 Stream 的 filter 方法,并告诉它筛选出 Stream 中 艺术家名字以 The 开头的数据,将筛选出的数据组织成一个新的 Stream 返回;紧接着,调用 Stream 的 map 方法,将 Stream 中的艺术家映射为艺术家的国籍,返回新的 Stream;最后,使用 Stream 的 collect 方法生成一个 Set 集合。

经过清单6 中的代码与清单 7 中的代码进行比较,我想大家会一致认同,Stream 简直太好用了,写出来的代码简洁易懂。

结束语


本篇博客只是简单介绍了 Stream 和 使用 Stream 解决问题的具体案例,在后续博客中,我会更加细致地介绍 Stream。

彩蛋


我今天要给大家介绍的是,我的学长勇哥,这个是他在开源中国的地址 https://my.oschina.net/silence88。勇哥是个完美的 Java 程序员,人帅,会做菜,弹的一手好吉他(他就是因为吉他与我嫂子相识的),会打篮球,最牛逼的还是写代码厉害。勇哥的博客值得一读,你们会看到一个屌丝程序员是如何打怪升级的。勇哥是在大四的第一个学期开始自学 Java 的,他现在顺丰的丰巢就职。

感谢大家的阅读~

© 著作权归作者所有

共有 人打赏支持
cnbo
粉丝 5
博文 8
码字总数 15846
作品 0
深圳
程序员
Java 8 新语法习惯 (提倡使用有帮助的编码)

表达能力强是函数式编程的优势之一,但是这对于我们的代码意味着什么呢?在这部分内容中,我们将比较命令式和函数式代码的示例,判断这两种的表达能力和简洁性的能力。我们还能够了解到这些品...

晁东洋 ⋅ 01/08 ⋅ 0

Java8 学习笔记

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

xrzs ⋅ 2013/05/14 ⋅ 0

Java8解决了什么?

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

MageekChiu ⋅ 2017/08/02 ⋅ 0

双11Java程序员书单推荐

Java 《Java核心技术卷I》 《Java核心技术卷II》 《Java编程思想》 《Java并发编程实战》 《Effective Java》 《Java8实战》 《Java8函数式编程思维》 《深入理解Java虚拟机》 《Java性能权威...

黄步欢 ⋅ 2017/11/04 ⋅ 0

Lambda表达式过滤长度低于1分钟的歌曲

这是《java8函数式编程》第三章中的内容,学习了对流的几个操作后,对它们的组合使用进行练习。 1、目的:给定了构建模型,对传入参数专辑列表ArrayList进行过滤,找出时间长度>60的歌曲名称...

caoxiaohong1005 ⋅ 2017/12/16 ⋅ 0

Java函数式开发——优雅的Optional空指针处理

那些年困扰着我们的null 在Java江湖流传着这样一个传说:直到真正了解了空指针异常,才能算一名合格的Java开发人员。在我们逼格闪闪的java码字符生涯中,每天都会遇到各种null的处理,像下面...

随风溜达的向日葵 ⋅ 2016/08/28 ⋅ 24

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

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

kisscatforever ⋅ 03/15 ⋅ 0

【java8】java新特性(二)——lambda表达式

一,前言 在上一篇博客中,小编向大家抛转引玉,简单说明了[ Java8 ](http://blog.csdn.net/kisscatforever/article/details/79572194 ),其实Java 8在2014年3月18日,就发布了。可以说程序...

kisscatforever ⋅ 03/20 ⋅ 0

利用java8新特性实现类似javascript callback特性

Java8的新特性之一,就是首次引入了函数式编程Lambda表达式,按oracle的说法,是为了引导java向函数式编程的方向发展。 在JDK1.8中,多了一个包,java.util.function,这里主要用到了这个包下...

Acce1erator ⋅ 2015/12/02 ⋅ 3

Java系列 - 用Java8新特性进行Java开发太爽了

本人博客文章网址:https://www.peretang.com/using-java8s-new-features-to-coding-is-awesome/ 前言 从开始写博客到现在已经过去3个月了. 并且保持着每周更新一次的规律. 这挺好的, 但是每次...

PereTang ⋅ 2017/07/12 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

一篇文章学懂Shell脚本

Shell脚本,就是利用Shell的命令解释的功能,对一个纯文本的文件进行解析,然后执行这些功能,也可以说Shell脚本就是一系列命令的集合。 Shell可以直接使用在win/Unix/Linux上面,并且可以调用...

Jake_xun ⋅ 26分钟前 ⋅ 0

大数据工程师需要精通算法吗,要达到一个什么程度呢?

机器学习是人工智能的一个重要分支,而机器学习下最重要的就是算法,本文讲述归纳了入门级的几个机器学习算法,加大数据学习群:716581014一起加入AI技术大本营。 1、监督学习算法 这个算法由...

董黎明 ⋅ 59分钟前 ⋅ 0

Kylin 对维度表的的要求

1.要具有数据一致性,主键值必须是唯一的;Kylin 会进行检查,如果有两行的主键值相同则会报错。 2.维度表越小越好,因为 Kylin 会将维度表加载到内存中供查询;过大的表不适合作为维度表,默...

无精疯 ⋅ 今天 ⋅ 0

58到家数据库30条军规解读

军规适用场景:并发量大、数据量大的互联网业务 军规:介绍内容 解读:讲解原因,解读比军规更重要 一、基础规范 (1)必须使用InnoDB存储引擎 解读:支持事务、行级锁、并发性能更好、CPU及...

kim_o ⋅ 今天 ⋅ 0

代码注释中顺序更改 文件读写换行

`package ssh; import com.xxx.common.log.LogFactory; import com.xxx.common.log.LoggerUtil; import org.apache.commons.lang3.StringUtils; import java.io.*; public class DirErgodic ......

林伟琨 ⋅ 今天 ⋅ 0

linux实用操作命令

参考 http://blog.csdn.net/qwe6112071/article/details/50806734 ls [选项] [目录名 | 列出相关目录下的所有目录和文件 -a 列出包括.a开头的隐藏文件的所有文件-A 同-a,但不列出"."和"...

简心 ⋅ 今天 ⋅ 0

preg_match处理中文符号 url编码方法

之前想过直接用符号来替换,但失败了,或者用其他方式,但有有些复杂,这个是一个新的思路,亲测可用 <?php$str='637朗逸·超速新风王(300)(白光)'; $str=iconv("UTF-8","GBK",$s...

大灰狼wow ⋅ 今天 ⋅ 0

DevOps 资讯 | PostgreSQL 的时代到来了吗 ?

PostgreSQL是对象-关系型数据库,BSD 许可证。拼读为"post-gress-Q-L"。 作者: Tony Baer 原文: Has the time finally come for PostgreSQL?(有删节) 近30年来 PostgreSQL 无疑是您从未听...

RiboseYim ⋅ 今天 ⋅ 0

github太慢

1:用浏览器访问 IPAddress.com or http://tool.chinaz.com 使用 IP Lookup 工具获得github.com和github.global.ssl.fastly.net域名的ip地址 2:/etc/hosts文件中添加如下格式(IP最好自己查一...

whoisliang ⋅ 今天 ⋅ 0

非阻塞同步之 CAS

为解决线程安全问题,互斥同步相当于以时间换空间。多线程情况下,只有一个线程可以访问同步代码。这种同步也叫阻塞同步(Blocking Synchronization). 这种同步属于一种悲观并发策略。认为只...

长安一梦 ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部