На информационном ресурсе применяются рекомендательные технологии (информационные технологии предоставления информации на основе сбора, систематизации и анализа сведений, относящихся к предпочтениям пользователей сети "Интернет", находящихся на территории Российской Федерации)

Использование Groovy: Замыкания и динамические объекты

Я продолжаю цикл заметок о Groovy - динамическом языке программирования для платформы Java.

В этой заметке я расскажу о замыканиях в языке Groovy, а также об одном из их применений - динамических объектах.

Замыкания

Замыкания можно представлять, как блоки кода (или анонимные функции), которые при выполнении имеют доступ к переменным того контекста, в котором они были объявлены.

С другой стороны, замыкания являются объектами, которые могут быть переданы в другие методы, сохранены в переменных, и т.п.

В Java аналогами замыканий являются анонимные классы, однако, они имеют намного менее удобный для использования синтаксис и имеют некоторые ограничения (которые, конечно можно обойти, но… ).

 

Таким образом, методы могут принимать блоки кода. Например, чтобы обработать каждую строку какого-то файла в Java надо написать достаточно объемный код. В Groovy. это делается одной строкой:

new File('file.txt').eachLine( { line -> println line } ) // Распечатываем каждую строку файла

Метод eachLine класса File для каждой строки файла вызвает замыкание, переданное в качестве аргумента, и передает в него содержимое строки.

В этом примере стоит обратить внимание на конструкцию { line -> println line } - это и есть объявление замыкания. Можно рассматривать его как функцию, которая принимает один аргумент line и распечатывает его.

Стоит отметить, что Groovy позволяет несколько более удобную запись, если замыкание является последним аргументом метода:

new File('file.txt').eachLine() { line -> println line } // Замыкание за скобками
 
new File('file.txt').eachLine { line -> println line } // Или без скобок вообще

Чтобы объявить замыкание, принимающее несколько параметров их надо перечислить через запятую, а чтобы определить замыкание без параметров, -> опускается.

Например:

def plus = { a, b -> a + b }
def do = { println "done" }

Как написать методы, использующие замыкания?

Пример:

def forEvery3rd( list, block ) {
def i = 0
for ( e in list ) {
if ( i % 3 == 0 )
block( i )
 
i ++
}
}

Эта функция принимает в качестве аргумента список и замыкание, и вызывает замыкание для каждого третьего элемента списка.

Использование контекста объявления

Одной из важных особенностью замыкания является возможность использования контекста, в котором оно было объявлено, например:

def str = '123'
def count = 0
new File('file.txt').eachLine { line -> if ( line == str ) count ++ }
println count

Этот код считает количество строк в файле, которые равны str. Причем, переменные str и count, используемые в замыкании объявлены вне его.

Возвращаемые значения

Замыкания всегда имеют возвращаемые значения. Возвращаемое значение определяется или оператором return, или же, если его нет, последним вычисленным выражением.

Встроенные переменные

В любых замыканиях всегда определены несколько переменных, имеющих специальный смысл:

  • it - это единственный аргумент замыкания. Использование этой переменной позволяет опускать объявление аргументов в замыкании. Например:
    def pr = { println it } // Замыкание, распечатывающее свой первый аргумент
  • this - это ссылка на класс, в котором объявлено замыкание
  • owner - это объект, содержащий контекст в котором объявлено замыкание. Значение - или this, если замыкание объявлено внутри класса или метода, или замыкание, внутри которого находится объявление замыкания.
  • delegate - то же самое, что и owner, однако может быть переопределено, что используется в Builders

Curring

Groovy поддерживает такую возможность, как curring, т.е. возможность получить новое замыкание, зафиксировав часть аргументов старого. Например:

def add = { a, b -> a + b } // Замыкание - сложение двух объектов
def add1 = add.curry( 1 ) // Замыкание - сложение единицы и одного из объектов

Динамические объекты (Expando)

Замыкания позволяют Groovy содержать поддержку динамических объектов, называемых Expando. Суть этих объектов в том, что они не имеют предопределенных полей и методов, однако они могут быть определены прямо в процессе выполнения:

def obj = new Expando();
obj.a = 1 // Создаем новое поле
obj.b = 2 // Создаем новое поле
obj.do = { println "done" } // Добавляем новый метод к объекту

Стоит заметить, что добавление методов к такому объекту есть ни что иное, как создание нового замыкания и сохранение его в объекте. В принципе, динамические объекты очень похожи на maps(отображения), однако отличаются тем, что могут содержать методы.

Рекомендуем
Популярное
наверх