关注公众号(CoderBuff)回复“stream”获取《Java8 Stream编码实战》PDF完整版。

距离Java 8发布已经过去了7、8年的时间,Java 14也刚刚发布。Java 8中关于函数式编程和新增的Stream流API至今饱受“争议”。

如果你不曾使用Stream流,那么当你见到Stream操作时一定对它发出过鄙夷的声音,并在心里说出“这都写的什么玩意儿”。

如果你热衷于使用Stream流,那么你一定被其他人说过它可读性不高,甚至在codereview时被要求改用for循环操作,更甚至被写入公司不规范编码中的案例。

这篇文章将告诉你,不要再简单地认为Stream可读性不高了!

下面我将围绕以下举例数据说明。

这里有一些学生课程成绩的数据,包含了学号、姓名、科目和成绩,一个学生会包含多条不同科目的数据。

ID 学号 姓名 科目 成绩
1 20200001 Kevin 语文 90
2 20200002 张三 语文 91
3 20200001 Kevin 数学 99
4 20200003 李四 语文 76
5 20200003 李四 数学 71
6 20200001 Kevin 英语 68
7 20200002 张三 数学 88
8 20200003 张三 英语 87
9 20200002 李四 英语 60

场景一:通过学号,计算一共有多少个学生?

通过学号对数据去重,如果在不借助Stream以及第三方框架的情况下,应该能想到通过Map的key键不能重复的特性循环遍历数据,最后计算Map中键的数量。

/**
* List列表中的元素是对象类型,使用For循环利用Map的key值不重复通过对象中的学号字段去重,计算有多少学生
* @param students 学生信息
*/
private void calcStudentCount(List<Student> students) {
Map<Long, Student> map = new HashMap<>();
for (Student student : students) {
map.put(student.getStudentNumber(), student);
}
int count = map.keySet().size();
System.out.println("List列表中的元素是对象类型,使用For循环利用Map的key值不重复通过对象中的学号字段去重,计算有多少学生:" + count);
}

你可能会觉得这很简洁清晰,但我要告诉你,这是错的!上述代码除了方法名calcStudentCount以外,冗余的for循环样板代码无法流畅传达程序员的意图,程序员必须阅读整个循环体才能理解。

接下来我们将使用Stream来准确传达程序员的意图。

Stream中distinct方法表示去重,这和MySQL的DISTINCT含义相同。Stream中,distinct去重是通过通过流元素中的hashCode()equals()方法去除重复元素,如下所示通过distinct对List中的String类型元素去重。

private void useSimpleDistinct() {
List<String> repeat = new ArrayList<>();
repeat.add("A");
repeat.add("B");
repeat.add("C");
repeat.add("A");
repeat.add("C"); List<String> notRepeating = repeat.stream().distinct().collect(Collectors.toList());
System.out.println("List列表中的元素是简单的数据类型:" + notRepeating.size());
}

再调用完distinct方法后,再调用collect方法对流进行最后的计算,使它成为一个新的List列表类型。

但在我们的示例中,List中的元素并不是普通的数据类型,而是一个对象,所以我们不能简单的对它做去重,而是要先调用Stream中的map方法。

/**
* List列表中的元素是对象类型,使用Stream利用HashMap通过对象中的学号字段去重,计算有多少学生
* @param students 学生信息
*/
private void useStreamByHashMap(List<Student> students) {
long count = students.stream().map(Student::getStudentNumber).distinct().count();
System.out.println("List列表中的元素是对象类型,使用Stream利用Map通过对象中的学号字段去重,计算有多少学生:" + count);
}

Stream中的map方法不能简单的和Java中的Map结构对应,准确来讲,应该把Stream中的map操作理解为一个动词,含义是归类。既然是归类,那么它就会将属于同一个类型的元素化为一类,学号相同的学生自然是属于一类,所以使用map(Student::getStudentNumber)将学号相同的归为一类。在通过map方法重新生成一个流过后,此时再使用distinct中间操作对流中元素的hashCode()equals()比较去除重复元素。

另外需要注意的是,使用Stream流往往伴随Lambda操作,有关Lambda并不是本章的重点,在这个例子中使用map操作时使用了Lambda操作中的“方法引用”——Student::getStudentNumber,语法格式为“ClassName::methodName”,完整语法是“student -> student.getStudentNumber()”,它表示在需要的时候才会调用,此处代表的是通过调用Student对象中的getStudentNumber方法进行归类。

场景二:通过学号+姓名,计算一共有多少个学生?

传统的方式依然是借助Map数据结构中key键的特性+for循环实现:

/**
* List列表中的元素是对象类型,使用For循环利用Map的key值不重复通过对象中的学号+姓名字段去重,计算有多少学生
* @param students 学生信息
*/
private void useForByMap(List<Student> students) {
Map<String, Student> map = new HashMap<>();
for (Student student : students) {
map.put(student.getStudentNumber() + student.getStudentName(), student);
}
int count = map.keySet().size();
System.out.println("List列表中的元素是对象类型,使用For循环利用Map的key值不重复通过对象中的学号+姓名字段去重,计算有多少学生:" + count); }

如果使用Stream流改动点只是map操作中的Lambda表达式:

/**
* List列表中的元素是对象类型,使用Stream利用HashMap通过对象中的学号+姓名字段去重,计算有多少学生
* @param students 学生信息
*/
private void useStreamByHashMap(List<Student> students) {
long count = students.stream().map(student -> (student.getStudentNumber() + student.getStudentName())).distinct().count();
System.out.println("List列表中的元素是对象类型,使用Stream利用Map通过对象中的学号+姓名字段去重,计算有多少学生:" + count);
}

前面已经提到在使用map时,如果只需要调用一个方法则可以使用Lambda表达式中的“方法引用”,但这里需要调用两个方法,所以只好使用Lambda表达式的完整语法“student -> (student.getStudentNumber() + student.getStudentName())”。

这个场景主要是熟悉Lambda表达式。

场景三:通过学号对学生进行分组,例如:Map<Long, List>,key=学号,value=学生成绩信息

传统的方式仍然可以通过for循环借助Map实现分组:

/**
* 借助Map通过for循环分类
* @param students 学生信息
*/
private Map<Long, List<Student>> useFor(List<Student> students) {
Map<Long, List<Student>> map = new HashMap<>();
for (Student student : students) {
List<Student> list = map.get(student.getStudentNumber());
if (list == null) {
list = new ArrayList<>();
map.put(student.getStudentNumber(), list);
}
list.add(student);
}
return map;
}

这种实现比场景一更为复杂,充斥着大量的样板代码,同样需要程序员一行一行读for循环才能理解含义,这样的代码真的可读性高吗?

来看Stream是如何解决这个问题的:

/**
* 通过Group分组操作
* @param students 学生信息
* @return 学生信息,key=学号,value=学生信息
*/
private Map<Long, List<Student>> useStreamByGroup(List<Student> students) {
Map<Long, List<Student>> map = students.stream().collect(Collectors.groupingBy(Student::getStudentNumber));
return map;
}

一行代码搞定分组的场景,这样的代码可读性不高吗?

场景四:过滤分数低于70分的数据,此处“过滤”的含义是排除掉低于70分的数据

传统的for循环样板代码,想都不用想就知道直接在循环体中加入if判断即可:

/**
* 通过for循环过滤
* @param students 学生数据
* @return 过滤后的学生数据
*/
public List<Student> useFor(List<Student> students) {
List<Student> filterStudents = new ArrayList<>();
for (Student student : students) {
if (student.getScore().compareTo(70.0) > 0) {
filterStudents.add(student);
}
}
return filterStudents;
}

使用Stream流,则需要使用心得操作——filter

/**
* 通过Stream的filter过滤操作
* @param students 学生数据
* @return 过滤后的学生数据
*/
public List<Student> useStream(List<Student> students) {
List<Student> filter = students.stream().filter(student -> student.getScore().compareTo(70.0) > 0).collect(Collectors.toList());
return filter;
}

filter中的Lambda表达式如果返回true,则包含进此次结果中,如果返回false则排除掉

以上关于Stream流的操作,你真的还认为Stream的可读性不高吗?

关注公众号(CoderBuff)回复“stream”获取《Java8 Stream编码实战》PDF完整版。

这是一个能给程序员加buff的公众号 (CoderBuff)

不要再认为Stream可读性不高了!的更多相关文章

  1. 构建可读性更高的 ASP.NET Core 路由

    原文:构建可读性更高的 ASP.NET Core 路由 一.前言 不知你在平时上网时有没有注意到,绝大多数网站的 URL 地址都是小写的英文字母,而我们使用 .NET/.NET Core MVC 开发 ...

  2. UITableView!别再用代码计算行高了(一)

    你还在用代码去计算行高吗?你不感觉那种方式很low吗?从今天起,试着做些改变吧! 别给我讲你喜欢写代码的感觉,你就是要用代码去计算行高,那我这篇文章不适合你. 在讲解复杂内容之前,还是先学习简单的内容 ...

  3. 再一次生产 CPU 高负载排查实践

    前言 前几日早上打开邮箱收到一封监控报警邮件:某某 ip 服务器 CPU 负载较高,请研发尽快排查解决,发送时间正好是凌晨. 其实早在去年我也处理过类似的问题,并记录下来:<一次生产 CPU 1 ...

  4. 再谈VS2010编译更高平台vs2012(v110),vs2015(v140)的objectARX程序

    前段时间我贴了一篇vs2010批量编译vc6~vs2008的ARX版本,实际上那一篇是我在研究vs2010编译v110,v140平台的附带收获,正应了那句话,有心栽花花不开,无心插柳柳成荫,因为vs2 ...

  5. OO第一次阶段性总结

    经过三次作业的历练之后终于来到了写博客这一周.回顾开学来的这一个月,令我印象最深刻也是最累的一门课就是OO了.虽然上学期学过一部分Java,但这学期开学就来的OO作业还是让我在第二周就开始熬夜了.不过 ...

  6. 使用Swagger 搭建高可读性ASP.Net WebApi文档

    一.前言 在最近一个商城项目中,使用WebApi搭建API项目.但开发过程中,前后端工程师对于沟通接口的使用,是非常耗时的.之前也有用过Swagger构建WebApi文档,但是API文档的可读性并不高 ...

  7. Java 8:不要再用循环了 Stream替代for循环

    原文:http://www.importnew.com/14841.html 在这篇文章里,我们将会去了解传统循环的一些替代方案.在Java 8的新功能特性中,最棒的特性就是允许我们去表达我们想要完成 ...

  8. Java8 Stream流

    第三章 Stream流 <Java8 Stream编码实战>的代码全部在https://github.com/yu-linfeng/BlogRepositories/tree/master ...

  9. 【腾讯Bugly干货分享】让 CodeReview 这股清流再飞一会儿

    本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/ToYeT4Y4pzx0ii9Z92fo-Q 作者:刘 ...

随机推荐

  1. Python Web 基础向(四) 浅谈数据层

    数据层一般会给人带来一些困扰,在于其定位不准确.聚合Model的工作也可以放在逻辑层做,但会导致逻辑层变重,经常出现大段晦涩代码.因此我的建议是保留Model聚合层,尽管会导致工作量的略微增加,但却可 ...

  2. MyBatis XML 配置文件 properties 元素扩展

    在分析 MyBatis XML 配置文件 properties 元素时提到了三种配置方式,其中 property 子元素 和 properties 文件都比较容易理解,但是为什么还要提供一种代码参数传 ...

  3. 用了python多进程,我跑程序花费的时间缩短了4倍

    应用场景:本人需要对200万条网页html格式数据进行清洗,提取文字后将分词结果写入数据库,之前做了一次,大概花费了80多个小时才跑完.机器配置是4核,内存8G:开完会领导让再改点东西重新跑一遍,然后 ...

  4. OpenStack入门

    云计算优势 降低成本,安全稳定,易扩展. 云计算三种服务模式 IaaS:基础设施即服务 IaaS(Infrastructure-as-a- Service):基础设施即服务.消费者通过Internet ...

  5. 添砖加瓦:Linux系统监测

    前言 前段时间因为项目需求,需要实时获取系统当前的运行状态,遂查阅了不少资料,基于/proc目录下的部分文件,实现了系统CPU.内存.网络和磁盘的实时监测. 一.CPU使用情况获取 获取CPU使用情况 ...

  6. ES插件升级

    #!/bin/bash mkdir -p /home/esuser cd /home/esuser wget http://10.12.xx.xx:8090/search_plugins/sd_wai ...

  7. 01Java代码是怎么运行的

    从虚拟机视角来看,执行 Java 代码首先需要将它编译而成的 class 文件加载到 Java 虚拟机中.加载后的 Java 类会被存放于方法区(Method Area)中.实际运行时,虚拟机会执行方 ...

  8. Python Mock 的入门

    Mock是什么 Mock这个词在英语中有模拟的这个意思,因此我们可以猜测出这个库的主要功能是模拟一些东西.准确的说,Mock是Python中一个用于支持单元测试的库,它的主要功能是使用mock对象替代 ...

  9. Vimium - 让你体验Geek般的浏览体验

    相信很多电脑高手们都会寻找一一些快捷高效的操作方式,如经常利用键盘的快速操作,让你脱离鼠标,可以让你不用花太多精力地去移动细小的指针进行操作,使得工作的效率提高许多. 不过,实际上很多时候我们还是不得 ...

  10. vue配合iview/element等ui实现界面效果起步

    iview与element都是与vue配合使用的ui框架,用法与配置基本一致,在此,我以iview为例,教你如何起步.*首先,你需要有一定的vue基础,如果你还是个小白,可以去我之前介绍如何搭建一个v ...