Лучший шаблон для имитации «продолжить» в замыкании Groovy

StackOverflow https://stackoverflow.com/questions/205660

  •  03-07-2019
  •  | 
  •  

Вопрос

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

revs.eachLine { line -> 
    if (line ==~ /-{28}/) {
         // continue to next line...
    }
}
Это было полезно?

Решение

Можно только поддерживать, продолжать чисто, а не ломать.Особенно с такими вещами, как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
    }
}

другой вариант имитирует то, что обычно делает 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.

Другие советы

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

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

}

Поддержка прерывания не происходит на уровне замыкания, а подразумевается семантикой вызова метода, принимающего замыкание.Короче говоря, это означает, что вместо вызова «each» для чего-то вроде коллекции, которая предназначена для обработки всей коллекции, вы должны вызвать find, который будет обрабатываться до тех пор, пока не будет выполнено определенное условие.В большинстве (всех?) случаев вы чувствуете необходимость выйти из замыкания, а на самом деле вам нужно найти конкретное условие во время итерации, благодаря которому метод поиска будет соответствовать не только вашим логическим потребностям, но и вашему намерению.К сожалению, некоторые API не поддерживают метод поиска...Файл например.Вполне возможно, что все время, потраченное на споры о том, должен ли язык включать прерывание/продолжение, могло быть потрачено с пользой на добавление метода find в эти игнорируемые области.Что -то вроде первой дирмирования (закрытие C) или FindLineMatching (закрытие C) будет иметь большое значение и ответит на 99+% от «Почему я не могу вырваться от ...?» Вопросы, которые появляются в списках рассылки.Тем не менее, добавить эти методы самостоятельно через MetaClass или Категории тривиально.

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}/ }

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

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

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

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

Использовать возвращаться к продолжать и любой закрытие перерыв.

Пример

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

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

Отличный код:

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

    println line

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

Выход:

1
2
3

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

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

Тогда вы можете заменить продолжать с фильтр и перерыв с взять Пока

Вот пример:

import rx.Observable

Observable.from(1..100000000000000000)
          .filter { it % 2 != 1} 
          .takeWhile { it<10 } 
          .forEach {println it}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top