Лучший шаблон для имитации «продолжения» в Groovy замыкании - PullRequest
67 голосов
/ 15 октября 2008

Кажется, что Groovy не поддерживает break и continue из замыкания. Каков наилучший способ симулировать это?

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

Ответы [ 6 ]

71 голосов
/ 15 октября 2008

Вы можете только поддерживать продолжение чисто, а не перерыв. Особенно с такими вещами, как eachLine и каждый. Неспособность поддержать разрыв связана с тем, как оцениваются эти методы, не учитывается, что не завершается цикл, который может быть передан методу. Вот как поддержать продолжение -

Лучший подход (при условии, что вам не нужно полученное значение).

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

Если ваш образец действительно такой простой, это хорошо для удобства чтения.

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

другая опция, имитирует то, что обычно делает continue на уровне байт-кода.

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.

15 голосов
/ 18 октября 2008

Замыкания не могут прерываться или продолжаться, потому что они не являются конструкциями цикла / итерации. Вместо этого они являются инструментами, используемыми для обработки / интерпретации / обработки итеративной логики. Вы можете игнорировать данные итерации, просто возвращаясь из замыкания без обработки, как в:

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

}

Поддержка разрыва не происходит на уровне замыкания, а подразумевается семантикой вызова метода, принимающего замыкание. Короче говоря, это означает, что вместо вызова «each» для чего-то вроде коллекции, которая предназначена для обработки всей коллекции, вы должны вызвать 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}/ }

Другие хаки, включающие исключения и другое волшебство, могут работать, но в некоторых ситуациях приводить к дополнительным издержкам и усложнять читаемость в других. Правильный ответ - взглянуть на ваш код и спросить, действительно ли вы выполняете итерацию или выполняете поиск.

10 голосов
/ 24 мая 2010

Если вы предварительно создали статический объект Exception в Java и затем выбросили (статическое) исключение из замыкания, затраты времени выполнения минимальны. Реальные затраты связаны с созданием исключения, а не с его выдачей. По словам Мартина Одерски (изобретателя Scala), многие JVM могут на самом деле оптимизировать инструкции броска для одиночных прыжков.

Это может быть использовано для имитации перерыва:

final static BREAK = new Exception();
//...
try {
  ... { throw BREAK; }
} catch (Exception ex) { /* ignored */ }
8 голосов
/ 17 октября 2013

Использование возврат в продолжение и любое закрытие до разрыв

Пример

Содержимое файла:

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
3 голосов
/ 27 марта 2012

В этом случае вам, вероятно, следует подумать о методе find(). Он останавливается после первого переданного ему замыкания, возвращающего значение true.

1 голос
/ 02 июня 2015

С rx-java вы можете преобразовать итерацию в наблюдаемую.

Затем вы можете заменить continue на filter и break на takeWhile

Вот пример:

import rx.Observable

Observable.from(1..100000000000000000)
          .filter { it % 2 != 1} 
          .takeWhile { it<10 } 
          .forEach {println it}
...