BIGBIG
V2EX  ›  Java

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

  •  1
     
  •   BIGBIG · Nov 1, 2023 · 6705 views
    This topic created in 1107 days ago, the information mentioned may be changed or developed.

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

    现有一个数据清洗任务。有多层循环,如下伪代码。 我的问题是:当 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 replies  •  2024-03-14 14:28:00 +08:00
    sockpuppet9527
    sockpuppet9527
    Nov 1, 2023
    1
    本身,引入一个状态机?或在 caller 那边做个状态?
    chendy
    chendy
    Nov 1, 2023
    2
    代码目测没啥问题
    一层一层 reture 也没啥问题
    难道楼主需要 System.exit(0) ?
    elliottzhao87
    elliottzhao87
    Nov 1, 2023
    3
    在外部放一个变量,检测变量直接在外层跳出呗?
    irrigate2554
    irrigate2554
    Nov 1, 2023❤️1
    4
    抛出个 Runtime 异常,最外层捕获处理,当然这个是旁门左道,正常还是多层 return 吧。
    xwayway
    xwayway
    Nov 1, 2023❤️15
    5
    当然是 goto 啦,想去哪儿去哪儿
    zhlxsh
    zhlxsh
    Nov 1, 2023 via iPhone❤️1
    6
    @xwayway 蒙多哈,想去哪就去哪
    virusdefender
    virusdefender
    Nov 1, 2023❤️1
    7
    抛出特定的异常
    darkengine
    darkengine
    Nov 1, 2023
    8
    Java 的不知道啊。这个逻辑在 JS/TS 有问题,即使总数大于 1000000 也只是退出一个 foreach 代码块,没跑完的还是会空跑。
    broken123
    broken123
    Nov 1, 2023
    9
    @zhlxsh 就你最优秀
    broken123
    broken123
    Nov 1, 2023
    10
    直接问 chatgpt 即可
    lsk569937453
    lsk569937453
    Nov 1, 2023
    11
    用 stream.flatMap.limit 完美解决
    darkengine
    darkengine
    Nov 1, 2023
    12
    而且 foreach 空跑还会进入 cleanByCount ,现在的代码会导致 cleanedCount 不准确

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

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

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

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

    还是抛异常吧
    GeruzoniAnsasu
    GeruzoniAnsasu
    Nov 1, 2023❤️1
    19
    #7 +1:

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

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

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


    帖子下沉啦
    iosyyy
    iosyyy
    Nov 1, 2023
    25
    尽量减少循环
    baoshijiagong
    baoshijiagong
    Nov 1, 2023❤️1
    26
    要看情况,如果“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
    gg1025
    Nov 1, 2023
    27
    别问,问就是 goto
    KaGaMiKun
    KaGaMiKun
    Nov 1, 2023
    28
    第一反应是实现迭代器
    但这迭代器使用场景很少,仅仅可能为了这处省个返回才写的,顿时感觉还是不如老实返回
    billccn
    billccn
    Nov 2, 2023❤️1
    29
    抛异常就是正解,一些函数式编程语言(比如 OCaml )抛异常是从内层退出循环的唯一的方式,这些语言是研究计编程理论的人喜欢用的,他们都没觉得是抛异常是旁门左道。

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

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

    @Override
    public synchronized Throwable fillInStackTrace() {
    return this;
    }
    }
    leee41
    leee41
    Mar 14, 2024
    31
    goto label
    你看反编译出来的 class 就能经常看到了,这个真有用,你这个场景完美 cover
    leee41
    leee41
    Mar 14, 2024
    32
    代码补充
    ```
    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");
    ```
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Privacy   ·   Solana   ·   4144 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 92ms · UTC 05:58 · PVG 13:58 · LAX 21:58 · JFK 00:58
    ♥ Do have faith in what you're doing.