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

Также на эту тему

Ссылки

 Подписаться на RSS

 #  #  #  #  #  #  #  #  #  #

Добавить комментарий