V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
BIGBIG
V2EX  ›  Java

如何优雅的跳出多层循环?

  •  1
     
  •   BIGBIG · 2023-11-01 10:45:39 +08:00 · 3443 次点击
    这是一个创建于 382 天前的主题,其中的信息可能已经有所发展或是发生改变。

    如何优雅的跳出多层循环?

    现有一个数据清洗任务。有多层循环,如下伪代码。 我的问题是:当 cleanedCount 达到 1 百万条数据时,要停止整个清洗任务。难道要一层一层 return 出去么?

    class CleanData{
    
      // 已清洗数据量
      cleanedCount ;
    
      // 迁移任务
      method cleanByTask() {
        activityIdList.foreach{activityId -> cleanByActivity(activityId)}
      }
    
      // 按活动
      method cleanByActivity(activityId) {
        tables.foreach{table -> cleanByTable(table)}
      }
    
      // 按表
      method cleanByTable(table) {
        timeSplits.foreach{timeSplit -> cleanByTimeSplit(timeSplit)}
      }
      
      // 按时间切片
      method cleanByTimeSplit(timeSplit) {
        每 500 条.foreach{500 条 -> cleanByCount(500 条)}
      }
    
      // 按数量
      method cleanByCount(500 条) {
        cleanedCount += 500
        if (cleanedCount >= 1000000) {
          reurn;
        }
      }
    
    }
    
    32 条回复    2024-03-14 14:28:00 +08:00
    sockpuppet9527
        1
    sockpuppet9527  
       2023-11-01 10:52:21 +08:00
    本身,引入一个状态机?或在 caller 那边做个状态?
    chendy
        2
    chendy  
       2023-11-01 10:53:01 +08:00
    代码目测没啥问题
    一层一层 reture 也没啥问题
    难道楼主需要 System.exit(0) ?
    elliottzhao87
        3
    elliottzhao87  
       2023-11-01 10:53:11 +08:00
    在外部放一个变量,检测变量直接在外层跳出呗?
    xausky
        4
    xausky  
       2023-11-01 10:55:34 +08:00   ❤️ 1
    抛出个 Runtime 异常,最外层捕获处理,当然这个是旁门左道,正常还是多层 return 吧。
    xwayway
        5
    xwayway  
       2023-11-01 11:01:22 +08:00   ❤️ 15
    当然是 goto 啦,想去哪儿去哪儿
    zhlxsh
        6
    zhlxsh  
       2023-11-01 11:02:45 +08:00 via iPhone   ❤️ 1
    @xwayway 蒙多哈,想去哪就去哪
    virusdefender
        7
    virusdefender  
       2023-11-01 11:04:56 +08:00   ❤️ 1
    抛出特定的异常
    darkengine
        8
    darkengine  
       2023-11-01 11:07:14 +08:00
    Java 的不知道啊。这个逻辑在 JS/TS 有问题,即使总数大于 1000000 也只是退出一个 foreach 代码块,没跑完的还是会空跑。
    broken123
        9
    broken123  
       2023-11-01 11:08:23 +08:00
    @zhlxsh 就你最优秀
    broken123
        10
    broken123  
       2023-11-01 11:08:45 +08:00
    直接问 chatgpt 即可
    lsk569937453
        11
    lsk569937453  
       2023-11-01 11:11:05 +08:00
    用 stream.flatMap.limit 完美解决
    darkengine
        12
    darkengine  
       2023-11-01 11:15:53 +08:00
    而且 foreach 空跑还会进入 cleanByCount ,现在的代码会导致 cleanedCount 不准确

    method cleanByCount(500 条) {
    cleanedCount += 500
    if (cleanedCount >= 1000000) {
    reurn;
    }
    }

    改成
    method cleanByCount(500 条) {
    if (cleanedCount >= 1000000) {
    reurn;
    }
    cleanedCount += 500
    }
    pengtdyd
        13
    pengtdyd  
       2023-11-01 11:28:37 +08:00
    那当然是 kill -9 啦
    cailinunix
        14
    cailinunix  
       2023-11-01 11:29:00 +08:00
    尝试扁平化你的数据,把多层循环拍平成一个迭代器,然后就可以随便跳出了
    Aresxue
        15
    Aresxue  
       2023-11-01 11:33:32 +08:00
    所以说 goto 部分场景下还是有价值的。
    针对这个场景用流也是个不错的方案。
    nthin0
        16
    nthin0  
       2023-11-01 11:36:04 +08:00   ❤️ 1
    我选择抛特定异常。。一层层 return 要加的判断比较多,有点丑陋
    williamx
        17
    williamx  
       2023-11-01 11:37:59 +08:00   ❤️ 1
    绝大多数情况下,你这个是伪需求。每一层都有退出条件,退出时需要收尾,所以最内层退出后会自动一层层退出。

    如果代码写得有漏洞 / 某些层条件没有配置好不能修改或者不想修改 / 想走特殊的逻辑而不是原来正常的逻辑,那就是异常情况,使用异常处理。
    yazinnnn0
        18
    yazinnnn0  
       2023-11-01 11:39:30 +08:00   ❤️ 1
    java 有 goto 关键字, 但是这个关键字没有作用

    有 break label 的语法, 但是你拆成多个函数就没办法用了

    还是抛异常吧
    GeruzoniAnsasu
        19
    GeruzoniAnsasu  
       2023-11-01 11:45:24 +08:00   ❤️ 1
    #7 +1:

    method exceptionCaptured() {
    try {cleanByTask()} catch(CountLimitExceeded){}
    }

    #4
    > 抛出个 Runtime 异常,最外层捕获处理,当然这个是旁门左道,正常还是多层 return 吧。

    不是旁门左道,这种不就是 recoverable exceptions, 抛异常是对的
    Leviathann
        20
    Leviathann  
       2023-11-01 13:40:50 +08:00
    用 monad 的 flatMap
    nodejsexpress
        21
    nodejsexpress  
       2023-11-01 13:47:02 +08:00
    不用 for, 用 while, 多几个条件 and 一起就好了.
    adoal
        22
    adoal  
       2023-11-01 13:52:08 +08:00
    惰性求值,摊平成迭代器
    kyuuseiryuu
        23
    kyuuseiryuu  
       2023-11-01 14:06:57 +08:00 via iPhone
    do
    for each
    if something
    break
    while false
    BIGBIG
        24
    BIGBIG  
    OP
       2023-11-01 14:25:08 +08:00
    结案啦:1. 抛出指定异常; 2. 然后捕获异常。优雅永不过时
    感谢各位巨佬。


    帖子下沉啦
    iosyyy
        25
    iosyyy  
       2023-11-01 15:12:08 +08:00
    尽量减少循环
    baoshijiagong
        26
    baoshijiagong  
       2023-11-01 16:50:44 +08:00   ❤️ 1
    要看情况,如果“cleanedCount 达到 1 百万条数据” 是异常情况,那么用抛异常,属优雅;如果是正常流程,那么只是形式上的优雅,逻辑上不算。

    后者可以用方法函数,将多层的 cleanByXXX 的实现抽象成同一个抽象方法,在这个抽象方法判断 cleanedCount 即可。

    新建抽象方法:

    public <P, C> void clean(P parentId, Function<P, List<C>> getChildList, Consumer<C> cleanChild) {
    if (cleanedCount < 10_000_000) {
    List<C> childList = getChildList.apply(parentId);
    childList.forEach(cleanChild);
    }
    }

    比如 cleanByTask 改成:

    public void cleanByTask() {
    clean(null, p -> {
    // getActivityIdList
    return new ArrayList<>();
    }, this::cleanByActivity);
    }
    gg1025
        27
    gg1025  
       2023-11-01 17:37:03 +08:00
    别问,问就是 goto
    KaGaMiKun
        28
    KaGaMiKun  
       2023-11-01 17:40:28 +08:00
    第一反应是实现迭代器
    但这迭代器使用场景很少,仅仅可能为了这处省个返回才写的,顿时感觉还是不如老实返回
    billccn
        29
    billccn  
       2023-11-02 07:10:24 +08:00   ❤️ 1
    抛异常就是正解,一些函数式编程语言(比如 OCaml )抛异常是从内层退出循环的唯一的方式,这些语言是研究计编程理论的人喜欢用的,他们都没觉得是抛异常是旁门左道。

    有一些老程序员会说尽量不要抛异常,是因为收集堆栈信息会比较慢,但是很多编程语言现在都对异常做了优化。比如 Java 虚拟机能识别这种用于控制程序执行,而不是用于报告错误的异常,抛出这种异常的时候里面就不会有堆栈信息,与创建一个普通对象无异。
    mmdsun
        30
    mmdsun  
       2023-11-03 00:35:58 +08:00   ❤️ 1
    fillInStackTrace 抛异常,对性能影响很小。

    public final class StopException extends RuntimeException {
    public static final StopException INSTANCE = new StopException();

    @Override
    public synchronized Throwable fillInStackTrace() {
    return this;
    }
    }
    leee41
        31
    leee41  
       248 天前
    goto label
    你看反编译出来的 class 就能经常看到了,这个真有用,你这个场景完美 cover
    leee41
        32
    leee41  
       248 天前
    代码补充
    ```
    outer: // 这是一个标签
    for (int i = 0; i < 5; i++) {
    for (int j = 0; j < 5; j++) {
    if (i * j > 10) {
    System.out.println("Breaking from nested loop");
    break outer; // 跳出标签指定的循环
    }
    }
    }
    System.out.println("Exited loop");
    ```
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1743 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 16:26 · PVG 00:26 · LAX 08:26 · JFK 11:26
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.