在Groovy闭包中模拟“连续”的最佳模式


68

看来,Groovy的不支持break,并continue从封闭中。模拟此的最佳方法是什么?

revs.eachLine { line -> 
    if (line ==~ /-{28}/) {
         // continue to next line...
    }
}

Answers:


72

您只能支持继续干净地进行,不能中断。特别是像eachLine和each这样的东西。无法支持中断与那些方法的评估方式有关,没有考虑未完成可以传达给该方法的循环。这是继续支持的方法-

最佳方法(假设您不需要结果值)。

revs.eachLine { line -> 
    if (line ==~ /-{28}/) {
        return // returns from the closure
    }
}

如果您的样本确实如此简单,那么这对于提高可读性很有帮助。

revs.eachLine { line -> 
    if (!(line ==~ /-{28}/)) {
        // do what you would normally do
    }
}

另一种选择是模拟继续操作通常在字节码级别执行的操作。

revs.eachLine { line -> 
    while (true) {
        if (line ==~ /-{28}/) {
            break
        }
        // rest of normal code
        break
    }

}

支持中断的一种可能方法是通过异常:

try {
    revs.eachLine { line -> 
        if (line ==~ /-{28}/) {
            throw new Exception("Break")
        }
    }
} catch (Exception e) { } // just drop the exception

您可能希望使用自定义异常类型来避免掩盖其他真实异常,尤其是在该类中正在进行其他可能抛出真实异常的处理时,例如NumberFormatExceptions或IOExceptions。


34
使用异常控制程序流是一个坏主意。创建异常需要对调用堆栈进行快照,这非常昂贵。
John Flinchbaugh

6
如果您在抛出异常的情况下不执行任何操作,则如果覆盖了生成调用堆栈的方法,则不会这样做。这是自定义异常的优点。
shemnon

9
如果您使用闭包之类的闭包而不是将其视为循环构造,那么这也是解决该问题所需要付出的额外努力。上面的示例将从修复逻辑不清楚的总体意图以过滤线或查找线中受益。
悬崖

17

闭包不能中断或继续,因为它们不是循环/迭代构造。相反,它们是用于处理/解释/处理迭代逻辑的工具。您可以通过简单地从闭包中返回而不进行如下处理来忽略给定的迭代:

revs.eachLine { line -> 
    if (line ==~ /-{28}/) {
            return
    }

}

中断支持不会在闭包级别发生,而是由接受闭包的方法调用的语义所隐含。简而言之,这意味着您不必调用像集合这样的用于处理整个集合的东西上的“每个”,而应该调用find,它将一直处理到满足特定条件为止。大多数(所有?)时间,您觉得需要从闭合中打破,您真正想做的是在迭代过程中找到特定条件,这使find方法不仅符合您的逻辑需求,而且符合您的意图。遗憾的是,某些API缺少对find方法的支持...例如文件。可能所有花费在争论该语言是否应包含中断/继续上的时间都可以很好地用于向这些被忽略的区域添加查找方法。诸如firstDirMatching(Closure c)或findLineMatching(Closure c)之类的东西会走很长一段路,并回答99%以上的“我为什么不能摆脱……?”的问题。在邮件列表中弹出的问题。也就是说,自己通过MetaClass或Categories添加这些方法很简单。

class FileSupport {
   public static String findLineMatching(File f, Closure c) {
      f.withInputStream {
         def r = new BufferedReader(new InputStreamReader(it))
         for(def l = r.readLine(); null!=l; l = r.readLine())
             if(c.call(l)) return l
         return null
      }
   }
}

using(FileSupport) { new File("/home/me/some.txt").findLineMatching { line ==~ /-{28}/ }

涉及异常和其他魔术的其他黑客可能会起作用,但在某些情况下会带来额外的开销,而在其他情况下会混淆可读性。真正的答案是查看您的代码,然后询问您是否真正在迭代或搜索。


11

如果您使用Java预创建了一个静态Exception对象,然后从闭包内部抛出了(静态)异常,则运行时的成本将降至最低。实际成本是在创建异常而不是引发异常时产生的。根据Scala的发明者Martin Odersky的说法,许多JVM实际上可以优化单跳的抛出指令。

这可以用于模拟中断:

final static BREAK = new Exception();
//...
try {
  ... { throw BREAK; }
} catch (Exception ex) { /* ignored */ }

2
那……那真是一个好主意。将此与枚举结合起来,可以为每个枚举建立一个预先创建的异常。哎呀-在枚举中放置一个方法将其抛出。Condition.BAD_WEATHER.fire();
paulmurray,2010年

1
实际上,这个想法不是我的。马丁·奥德斯基(斯卡拉的发明者)显示了它作为他谈话解决同样的问题在斯卡拉。多年前,我曾在Rensselaer的数据结构课程中任教过一位教授,展示了一个不使用goto语句的解决方案,然后使用gotos进行了相同的解决方案。后者短得多。他的观点是,有时实用性会胜过优雅。
拉尔夫

10

使用回归继续任何封闭到打破

档案内容:

1
2
----------------------------
3
4
5

Groovy代码:

new FileReader('myfile.txt').any { line ->
    if (line =~ /-+/)
        return // continue

    println line

    if (line == "3")
        true // break
}

输出:

1
2
3

1
你真是天才 .any要小心。如果闭包中的最后一条语句与“ true”相关联,它将停止。如果您希望逻辑或要完全处理文件,请将'false'作为任何闭包内的最后一条语句。我花了几天的时间才看到这篇文章。
user2618844

2
如果您只用最后一个if块替换掉,line == "3"我想那会更清楚
Ed Norris

4

在这种情况下,您可能应该考虑该find()方法。第一次传递给它的闭包返回true后,它将停止。


1

使用rx-java,您可以将iterable转换为observable。

然后你就可以代替继续使用过滤器,并打破takeWhile

这是一个例子:

import rx.Observable

Observable.from(1..100000000000000000)
          .filter { it % 2 != 1} 
          .takeWhile { it<10 } 
          .forEach {println it}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.