别扯那些没用的系列之:forEach循环_weixin_34416649的博客-程序员秘密

技术标签: ViewUI  java  前端  数据库  

写Java代码的程序员,集合的遍历是常有的事,用惯了for循环、while循环、do while循环,我们来点别的,JDK8 使用了新的forEach机制,结合streams,让你的代码看上去更加简洁、更加高端,便于后续的维护和阅读。好,不说了,"talk is cheap, show me the code",我们直接上代码,秉承一贯以来的风格。skr~skr~

一、对常用集合的遍历

JDK8中的forEach不仅可以对集合元素进行遍历,也能根据集合元素的值搞些事情,比如做判断,比如过滤。我们拿常用的List和Map来举例。

对Map集合的遍历:

/**
 * 对Map的遍历
 */
Map<String, Integer> map = Maps.newHashMap();
map.put("天猫双11", 1024);
map.put("京东双11", 1024);
// ①简写式
map.forEach((k, v) -> System.out.println("key:" + k + ", value:" + v));
// ②判断式
map.forEach((k, v) -> {
    System.out.println("key:" + k + ", value:" + v);
    if (StringUtils.contains(k, "京东")) {
        System.out.println("skr~");
    }
});
复制代码

执行结果:

key:京东双11, value:1024
key:天猫双11, value:1024
key:京东双11, value:1024
skr~
key:天猫双11, value:1024
复制代码

对List集合的遍历:

/**
 * 对List的遍历
 */
List<String> list = Lists.newArrayList();
list.add("买买买");
list.add("剁剁剁");
// ①简写式
list.forEach(item -> System.out.println(item));
// ②判断式
list.forEach(item -> {
    if (StringUtils.contains(item, "买")) {
       System.out.println("不如再用两个肾换个iPhone XS Max Pro Plus也无妨啊~");
    }
});
复制代码

执行结果如下:

买买买
剁剁剁
不如再用两个肾换个iPhone XS Max Pro Plus也无妨啊~
复制代码

二、JDK8 中双冒号的使用

JDK8中有双冒号的用法,就是把方法当做参数传到stream内部,使stream的每个元素都传入到该方法里面执行一下。

比如,上面的List<String>的打印,我们可以这样写:

// JDK8 双冒号的用法
list.forEach(System.out::println);
复制代码

执行结果也是一样一样的:

买买买
剁剁剁
复制代码

在 JDK8 中,接口Iterable 8默认实现了forEach方法,调用了 JDK8 中增加的接口Consumer内的accept方法,执行传入的方法参数。 JDK源码如下:

/**
     * Performs the given action for each element of the {
     @code Iterable}
     * until all elements have been processed or the action throws an
     * exception.  Unless otherwise specified by the implementing class,
     * actions are performed in the order of iteration (if an iteration order
     * is specified).  Exceptions thrown by the action are relayed to the
     * caller.
     *
     * @implSpec
     * <p>The default implementation behaves as if:
     * <pre>{
     @code
     *     for (T t : this)
     *         action.accept(t);
     * }</pre>
     *
     * @param action The action to be performed for each element
     * @throws NullPointerException if the specified action is null
     * @since 1.8
     */
    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }
复制代码

三、对自定义类型的组装

这个用法我觉得是比较实用也比较常用的。我们先定义两个POJO,一个叫Track,是一个Entity,和我们的数据库表结构进行映射;另一个叫TrackVo,是一个Vo,在接口层返回给前端展示用。这里为了简化代码量,我们使用了lombok插件。好,先将它们定义出来:

Track.java

/**
 * @author huangzx
 * @date 2018/11/13
 */
@AllArgsConstructor
@Data
@Builder
public class Track {
    private Long id;
    private String name;
    private String anchor;
}
复制代码

TrackVo.java

/**
 * @author huangzx
 * @date 2018/11/13
 */
@AllArgsConstructor
@Data
@Builder
public class TrackVo {
    private Long trackId;
    private String trackName;
}
复制代码

经常遇到的场景就是:我通过一个Dao层将数据fetch出来,是一个List<Track>,但前端需要的是List<TrackVo>,但Track和TrackVo的字段又不一样。按照之前的做法,可能是直接用一个for循环或while循环将List<Track>遍历把里面的Entity赋值到TrackVo,你飞快地敲击键盘,伴随着屏幕的震动,十来行代码顿时被创造了出来,长舒一口气,大功告成!

殊不知,JDK8 自从引入新的forEach,结合streams,可以让这十来行代码浓缩为一行,实在是简练。来瞧一瞧:

/**
 * 对自定义类型的组装
 */
List<Track> trackList = Lists.newArrayList();
Track trackFirst = Track.builder().id(1L).name("我的理想").anchor("方清平").build();
Track trackSecond = Track.builder().id(2L).name("台上台下").anchor("方清平").build();
trackList.add(trackFirst);
trackList.add(trackSecond);

List<TrackVo> trackVoList = trackList.parallelStream().map(track -> TrackVo.builder().trackId(track.getId()).trackName(track.getName()).build()).collect(Collectors.toList());
System.out.println(JSON.toJSONString(trackVoList));
复制代码

执行结果如下:

[{
    "trackId":1,"trackName":"我的理想"},{
    "trackId":2,"trackName":"台上台下"}]
复制代码

似不似和你预期的结果一样?

四、原理

ok,秉承程序员认识一件事物——“知其然必知其所以然”的原则。我们来分析一下forEach的实现原理。

首先,我们要了解一下上面用到的流 (streams) 概念,以及被动迭代器。

Java 8 最主要的新特性就是 Lambda 表达式以及与此相关的特性,如流 (streams)、方法引用 (method references)、功能接口 (functional interfaces)。正是因为这些新特性,我们能够使用被动迭代器而不是传统的主动迭代器,特别是 Iterable 接口提供了一个被动迭代器的缺省方法叫做 forEach()。缺省方法是 Java 8 的又一个新特性,是一个接口方法的缺省实现,在这种情况下,forEach() 方法实际上是用类似于 Java 5 这样的主动迭代器方式来实现的。

实现了 Iterable 接口的集合类 (如:所有列表 List、集合 map) 现在都有一个 forEach() 方法,这个方法接收一个功能接口参数,实际上传递给 forEach() 方法的参数是一个 Lambda 表达式。我们来编写一段使用 streams 的代码:

/**
 * @author huangzx
 * @date 2018/11/13
 */
public class StreamCountsTest {
    public static void main(String[] args) {
        List<String> words = Arrays.asList("natural", "flow", "of", "water", "narrower");
        long count = words.stream().filter(w -> w.length() > 5).count();
        System.out.println(count);
    }
}
复制代码

上面所示代码使用 Java 8 方式编写代码实现流管道计算,统计字符长度超过5的单词的个数。列表 words 用于创建流,然后使用过滤器对数据集进行过滤,filter() 方法只过滤出单词的字符长度,该方法的参数是一个 Lambda 表达式。最后,流的 count() 方法作为最终操作,得到应用结果。

我们再对自定义类型的组装那句代码作个解析,如下:

中间操作除了 filter() 之外,还有 distinct()、sorted()、map() 等,一般是对数据集的整理,返回值一般也是数据集。我们可以大致浏览一下它有哪些方法,如下:

总的来说,Stream 遵循 "做什么,而不是怎么去做" 的原则。在我们的示例中,描述了需要做什么,比如获得长单词并对它们的个数进行统计。我们没有指定按照什么顺序,或者在哪个线程中做。相反,循环在一开始就需要指定如何进行计算。

五、为什么要用它?

网上许多人说:JDK8 的 Lambda 表达式的性能不如传统书写方式的性能。那为何还要出现呢?JDK的新api和新语法有时并不是为了性能而去做极致优化的。从理论上来说,面向对象编程,性能相对面向过程肯定是降低的,但是可维护性或清晰度有了很大的提升。

所以一个特性用与不用,取决于你关注什么,当公司给你3个月时间去做功能实现的时候,显然不会有人花1个月去做性能优化,这时候更清晰合理的代码就很重要了,大多数时候性能问题不是来自于算法和api的平庸表现,而是出自各种系统的bug。

六、总结

总结一下上面讲了什么?首先是对于常见集合我们怎么用forEach去操作,并且介绍了双冒号的用法;然后基于一个已存在的集合怎么利用它产生一个新的集合,以封装成我们想要的数据;最后介绍了它的实现原理并阐释为何要用它的的原因。好了,下课。。。(老师~再见~~)

(完)
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_34416649/article/details/88029379

智能推荐

Graph Matching_AIWCZ的博客-程序员秘密

受严骏驰老师讲座内容启发,最近项目中一个关键问题才找到了一些解决途径,为了能够快速对Graph Matching的起源和发展现状有一定了解,从而形成知识体系来更好得解决问题,准备慢下来,把这些知识沉淀下来,写成一篇blog。(讲座链接:https://www.bilibili.com/video/BV1Zf4y1S7Zr)Graph Matching 属于一种组合优化问题,图这类数据结构形式在推荐系统、知识图谱等中最为常见,而在计算机视觉领域中,为解决少样本学习或目标检测类别长尾分布等,图结构也有较多工

启用SVN的分支管理_svn 拉分支自动复制权限_肉肉的RORO的博客-程序员秘密

前提使用SVN分支技术的大前提:理解并掌握SVN的基本操作。如果对SVN的基本操作不熟悉,或者存在理解上的问题,可能对分支的使用造成障碍。 (平时在工作中,有没有对SVN不理解的地方?) 一些基本概念:1.版本库版本库就是SVN服务器上,存放记录了文件、目录每一次变化的数据库。  2.工作副本工作副本(也叫工作拷贝,很多时候简写做WC——即Work

图像的掩膜操作_weixin_30536513的博客-程序员秘密

[TOC]注:原创不易,转载请务必注明原作者和出处,感谢支持!所涉及的APIvoid cv::filter2D( InputArray src, // 输入图像 OutputArray dst, // 输出图像 int ddepth, // 输出图像深度 InputArray kernel, // 掩膜矩阵(核) ...

Linux slab技术_菊厂码农的博客-程序员秘密

一、Slab定义在操作系统的运行过程中,经常涉及到大量对象的重复生成、使用和释放问题,对象生成算法的诞生,可以在很大程度上提高整个提供的性能。在LINUX中所用到的对象,比较典型的例子如inode、task_struct等,都有这些特点。一般来说,这类对象的种类相对稳定,每类对象的数据却是大量的,并且在初始化与析构时要做大量的工作,所占时间比例大大超过内存分配所占用的时间。因此,如果我们能够用...

Java中的“别名现象”_王大明Pro MAX的博客-程序员秘密

         在写文章内容之前,终于下定决心去阅读那本被很多人说是大部头的《Java编程思想(第四版)》,作为一个新晋的Java菜鸟,总觉得里边的知识确实挺难的,因为要有C语言和C++的基础,但是我对这两门语言的认识基本停留在只是知道有这两门语言的存在,具体的并没有多大的深入。此外,虽然很多人说这本书并不适合初学者学习,但是个人虽然还是初学者,虽然对里边的一些东西还不是有多大的理解(我说的是具...

Qt设置展示图片的透明度,半透明_drawpixmap 半透明_月上柳烧头的博客-程序员秘密

QPixmap function::drawBackground(const QString&amp; fileName){ QPixmap pix1_(fileName); QPixmap temp(pix1_.size()); temp.fill(Qt::transparent); QPainter p1(&amp;temp); p1.setCompositionMode(QPainter::CompositionMode_Source); p1..

随便推点

360前端星计划—深入CSS_weixin_34072637的博客-程序员秘密

深入CSS课程ppt链接一、CSS的版本(level)css Level 1css Level 2(CSS2.2规范)css Level 3Color Module Level 3Selectors Level 3Media QueriesFonts Level 3...在css2.2之前,把css所有标准放在一起去管理,这样推进起来版本升级比较难,后来在css3的版本中...

jquery如何获取innerHTML_jquery innerhtml_英俊帅比林的博客-程序员秘密

在js中,我们可以通过document.getElementById().innerHTML来获取指定id名的innerHTML,在jquery中,我们不能这样获取,替代方式是:$().html()直接就可以进行获取...

单步执行的原理_wsxqaz的博客-程序员秘密

在保护模式下,处理器会在一些特殊情况下产生 “异常”中断,也就是出现某种特定情况,就终止当前正在执行的程序,跳转到一个(由操作系统指定的)中断处理函数。根据 异常中断 的不同,以及其他情况,选择是否跳回原先执行的程序,或是启动其他程序,或是干脆宕机。 此表是处理器已经定下的,内部中断。中断向量号 触发原因0x00 除零错0x01 调试异常0x02 非可屏蔽中断 (NMI)0x03 断点

许多点之间连线最短 python实现_分散点最短距离连接_健忘主义的博客-程序员秘密

题目: 如下图所示,平面上有一些关键点集,现需要将所有点连接起来,使得任何要给点都可以和其他点连通(直接或者间接的连接),且连接线段的长度总和最短第一次写的错误的代码:def main(list1): ''' desc:每新增一个点,计算该店到之前所有点的最近距离并连线,以此类推 ''' def juli(a,b): '''计算两点之间的距离''' ...

matlab练习程序(寻找凸包,Graham扫描法)_weixin_34310369的博客-程序员秘密

  我不太清楚这个凸包在图像处理中到底会怎样的运用,因为这个好像更多的是计算几何或是图形学里面的东西。不过作为一个算法,我感觉还是有必要研究一下的。我主要的参考资料是《算法导论》的33.3和这个博客。  代码在这里,我只写了主要过程,过分细节的判断就省略了。这里是逆时针寻找:main.mclear all;close all;clc;img=ones(256,256...

C++/MFC-GDI绘图之CBitmap位图_mfc cbitmap创建位图_法萌的博客-程序员秘密

一、显示资源里的位图①载入图片:loadbitmap②创建设备上下文:CreateCompatibleDC③替换设备环境位图:SelectObject④复制位图:bitblt⑤释放对象⑥释放设备上下文示例代码:{ // TODO: 在此添加控件通知处理程序代码 CBitmap mybitmap; mybitmap.LoadBitmap(IDB_BITMAP3);