优化双层for循环

一、前言

        之前小永哥分享过关于lambda表达式的一些操作,主要是针对list的一些便捷操作,本次呢还是有关于集合的,我们研究一下双层循环。

        双层循环在平时开发过程中很常见,不过一提到双层循环,代表的就是代码可读性差、数据量大时性能低下等问题,这次我们就来玩一玩这个双层for循环,看看怎么能将一个日常的双层for循环场景代码进行优化。

二、需求描述

        我们以人员和部门例说明。

        部门描述:部门名称、部门编号

        人员描述:人员名称、工作所在地

        数据结构:目前的数据结构是部门对象中包含这所属人员信息。

        

package com.relation.depart;

import lombok.Data;

import java.util.List;

/**
 * @author huhy
 * @version 1.0
 * @Description:
 * @ClassName Date:2025/6/3 23:22
 */
@Data
public class Depart {
    private String name;
    private List<Person> personList;
}
package com.relation.depart;

import lombok.Data;

/**
 * @author huhy
 * @version 1.0
 * @Description:
 * @ClassName Date:2025/6/3 23:27
 */
@Data
public class Person {
    private String name;
    private String city;
}
package com.relation.depart;

import cn.hutool.core.collection.CollUtil;
import org.junit.jupiter.api.Test;

import java.util.Collections;
import java.util.List;

/**
 * @author huhy
 * @version 1.0
 * @Description:
 * @ClassName Date:2025/6/4 22:45
 */
public class DepartTest {

    /**
     * 功能描述:初始化部门数据
     * @Author:huhy
     * @Date: 2025/6/4 22:46
     */
    private List<Depart> initDepartData(){
        Person p1 = new Person();
        p1.setName("王虎");
        p1.setCity("北京");
        Person p2 = new Person();
        p2.setName("张三");
        p2.setCity("北京");
        Person p3 = new Person();
        p3.setName("李四");
        p3.setCity("天津");
        Person p4 = new Person();
        p4.setName("布隆");
        p4.setCity("南京");
        Person p5 = new Person();
        p5.setName("胡子");
        p5.setCity("南京");
        Person p6 = new Person();
        p6.setName("成武");
        p6.setCity("上海");
        Person p7 = new Person();
        p7.setName("柳屋");
        p7.setCity("上海");
        Person p8 = new Person();
        p8.setName("石方");
        p8.setCity("广州");
        List<Person> persons1 = CollUtil.newArrayList();
        persons1.add(p1);
        persons1.add(p2);
        persons1.add(p3);
        List<Person> persons2 = CollUtil.newArrayList();
        persons2.add(p4);
        persons2.add(p5);
        List<Person> persons3 = CollUtil.newArrayList();
        persons3.add(p6);
        persons3.add(p7);
        persons3.add(p8);
        List<Depart> departList = CollUtil.newArrayList();
        Depart depart1 = new Depart();
        depart1.setName("事业一部");
        depart1.setPersonList(persons1);
        Depart depart2 = new Depart();
        depart2.setName("事业二部");
        depart2.setPersonList(persons2);
        Depart depart3 = new Depart();
        depart3.setName("事业三部");
        depart3.setPersonList(persons3);
        departList.add(depart1);
        departList.add(depart2);
        departList.add(depart3);
        return departList;
    }

    @Test
    public void getPeronTest(){

    }
}

        目前需求是,找出部门下工作所在地是北京的人员,只返回人员姓名即可。

        方案1:双层for循环
@Test
    public void personTest(){
        List<String> personNameList = getPeronByCity("北京");
        System.out.println(JSON.toJSONString(personNameList));
    }

    private List<String> getPeronByCity(String cityName){
        List<String> nameList = CollUtil.newArrayList();
        List<Depart> departList = initDepartData();
        for (Depart depart : departList) {
            List<Person> personList = depart.getPersonList();
            for (Person person : personList) {
                String city = person.getCity();
                if(StringUtils.equals(city,cityName)){
                    nameList.add(person.getName());
                }
            }
        }
        return nameList;
    }

    

         方案1代码分析:大家一眼就看出来了,小永哥写的代码没有注释,没错,我故意没写注释,就是为了更明显的体现这段代码可读性差的特点。基本上很难一眼就能看出这段代码要做什么事情。下面新的循环方式尝试优化一下看看效果咋样。

        方案2:forEach
@Test
    public void personTest(){
        List<String> personNameList = getPeronByCity("北京");
        System.out.println(JSON.toJSONString(personNameList));
    }

    private List<String> getPeronByCity(String cityName){
        List<String> nameList = CollUtil.newArrayList();
        List<Depart> departList = initDepartData();
        departList.forEach(d->d.getPersonList().forEach(p->{
            if(p.getCity().equals(cityName)){
                nameList.add(p.getName());
            }
        }));
        return nameList;
    }

                

        使用forEach后代码确实比上一个方式少点了,不过好像可读性更差了,第一个花点时间还能明白是怎么回事,怎么用forEach以后,我连读的欲望都没有了(差点忘了是自己写的了,差点看吐了)。怎么就没有合适的解决方案了,不行,得再想想办法。

        在开始方案3之前,我想先分享一个关于stream的高阶函数,flatMap(),我们简单介绍一下flatMap()的作用,其实就是可以将多个stream合并起来,例如:

        


@Test
    public void test(){
        List<String> list1 = CollUtil.newArrayList();
        list1.add("张三");
        list1.add("李四");
        List<String> list2 = CollUtil.newArrayList();
        list2.add("王五");
        list2.add("赵六");
        list1.stream();
        List<String> list = Stream.of(list1, list2).flatMap(e -> e.stream()).collect(Collectors.toList());
        System.out.println(JSON.toJSONString(list));
    }

       

        大家可以看到, flatMap的作用就是可以将多个集合的stream合并起来,这个例子当然看不出来它有什么出彩的地方,对于这种将两个集合合并的操作,使用list自带的addAll也能实现,没什么稀奇的,但是既然我在这里单独介绍这个函数,说明它绝非浪得虚名,因为我们下边优化就需要用到,我们继续开展方案3。

        方案3:flatMap与map函数搭配

        优化分析:

        我们前两波越写越拉胯,我们得好好分析一下怎么拆分一下这个需求

        第一步:将部门信息中的人员信息收集起来。

        第二步:过滤这些人员信息中所在城市是北京的人。

        第三步:将符合要求的人员名称返回。

        对于以上三步,我们也有应对的方法。

        首先第一步收集,正好可以用到我们刚才提到的flatMap函数

        第二步过滤,不正好也有一个过滤的函数filter可以使用。

        至于第三步,我们先不下定论,暂定调用forEach来实现。下面我们来看一下改良后的代码

@Test
    public void personTest(){
        List<String> personNameList = getPeronByCity("北京");
        System.out.println(JSON.toJSONString(personNameList));
    }

    private List<String> getPeronByCity(String cityName){
        List<String> nameList = CollUtil.newArrayList();
        List<Depart> departList = initDepartData();
        departList.stream()
                .flatMap(d->d.getPersonList().stream())
                .filter(p->StringUtils.equals(p.getCity(),cityName))
                .forEach(p->nameList.add(p.getName()));
        return nameList;
    }

        

        通过优化,代码已经优化到了一行,而且可读性也非常好,因为只要了解了这些函数的含义,就可以一眼就看出程序要表达的意思,大大提高了可读性,而且双层循环我们也优化掉了一层,起码直观的从代码层面上来看,是这样的。

        我们观察一下,这个方法是最优了吗?还记得我们第三步说的是暂定使用forEach实现是吧,再观察一下,我们明明可以直接调用求值方法collect直接返回,为什么还要额外再创建一nameList的多余变量呢?大胆一点,我们是不是连departList也可以干掉了?搞一把......

@Test
    public void personTest(){
        List<String> personNameList = getPeronByCity("北京");
        System.out.println(JSON.toJSONString(personNameList));
    }

    private List<String> getPeronByCity(String cityName){
        return initDepartData().stream()
                .flatMap(d->d.getPersonList().stream())
                .filter(p->StringUtils.equals(p.getCity(),cityName))
                .map(p->p.getName())
                .collect(Collectors.toList());
    }

        好了,终于又变成了一行,真是值得高兴的事情。

        对了,刚才提到collect是求值方法,为什么是求值方法?那么flatMap、filter、map这些函数又是什么?

        大家发现了,由于篇幅有限,我计划在下一篇解释一下这两个概念。

三、结语

        本次我们主要介绍了怎么使用stream将双层循环优化,这个是很重要的,例如我们要重构或者优化旧代码,既然要重构了,过去的糟粕该去就要去掉,不然我们优化的意义又在哪儿呢?是吧,后面我会陆续分享一些lambda表达式,本次就到这儿,晚安..........

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值