Я продолжаю цикл заметок о 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(отображения), однако отличаются тем, что могут содержать методы.