学习 Java8 函数式编程 (三)

扯淡


在准备学 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 的,他现在顺丰的丰巢就职。

感谢大家的阅读~

转载于:https://my.oschina.net/u/2501837/blog/1524127

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值