Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Andrey Adamovich - Groovy 2 Cookbook - 2013.pdf
Скачиваний:
44
Добавлен:
19.03.2016
Размер:
26.28 Mб
Скачать

Using Groovy Language Features

If a class already implements Serializable, the @AutoClone annotation can be configured to use the Serialization style. This feature performs a deep copy

automatically, attempting to copy the entire tree of objects including array and list elements. The AutoCloneStyle.SERIALIZATION style has some limitations; it is generally slower and it doesn't support fields marked with final.

Defining code as data in Groovy

One of the things that attracted the Java crowd to Groovy has been the presence of closures in the language since its creation in 2003. Closures are a very powerful feature of Groovy

and one of the most widely used. It is important to understand them well to take full advantage of the language. In this recipe, we will try to demonstrate the beauty that closures add to the language.

Getting ready

At its core, a closure is an anonymous block of code, such as:

{ -> }

The previous snippet is actually a closure without body. It is, in fact, an object of type groovy.lang.Closure. As with every other object, a closure can be passed to other methods or even to other closures. However, a closure is also a method—a method with no associated class; therefore, it may have arguments and can return a value (yes, it can also return a closure). A closure always returns the value of the last statement in the body; the return keyword is not needed. The body of a closure is not executed until it gets called.

This is what a closure definition with an argument looks like:

def doubling = { arg1 -> println arg1 * 2 }

This closure just prints the doubled value of the argument. The closure is assigned to the variable doubling. The argument is not typed, and it gets used in the body of the closure, the statement after the -> symbol. The closure object can be passed to other methods as a parameter. For example, if we try to print it, we will actually call the toString method of the

Closure class:

println doubling

The previous line of code will yield something like the following:

closures$_run_closure1@76bb5e95

We can also pass closures to many methods (for example, each, collect, find, and so on) that Groovy makes available on collections and arrays:

[1,2,3,4].each(doubling)

104

www.it-ebooks.info

Chapter 3

The preceding code snippet will apply the passed closure to every element in the collection:

2

4

6

8

Now that we have a general idea about closures, we can look at how to use them to write more elegant and concise code. Closures normally contain less code, have little or no repetition, and can be re-used easily . This is because a closure can also be defined code as data.

How to do it...

A common way to display the conciseness of Groovy closures is to show some looping and iteration methods from the Groovy Collection API:

[1,2,3].each { println it }

The previous snippet is a great example of the power of closures. The code prints all the elements of a list by iterating on each element of the array, and calls the println command on each element. The astute reader is probably wondering what the it keyword is for; it is simply a reference to the current element. The snippet can be also expressed in a slightly more verbose way if we choose not to use it:

[1,2,3].each { element -> println element }

The it keyword is an implicit variable that works on closures, accepting a single argument. A very welcome syntactic sugar!

The each is a method from the enhanced java.lang.Object of the Groovy JDK and takes a closure a parameter. The closure contains the code that does actual stuff on each member of the list.

The Java equivalent of the former snippet would look as follows:

List<Integer> list = Arrays.asList(1,2,3); for (Integer it :list) {

System.out.println(it);

}

Another example of how a closure can help with refactoring the code is that it deals with resources that have to be disposed of once out of scope.

105

www.it-ebooks.info

Using Groovy Language Features

Let's create a class, ExpensiveResource, and assume that this resource has to be explicitly opened and closed once it has been used (very much like a JDBC connection):

class ExpensiveResource {

def open() { println 'opened!' }

def writeData(data) { println "data written! $data" } def close(){ println 'closed!'}

}

To use this class, we would write something like the following:

def e = new ExpensiveResource() try {

e.open() println e.data

}finally { e.close()

}

The open and close methods must be called every time we deal with the resource. Let's see how a closure simplifies and makes the code more polished:

def safeResource(Closure closure) {

def resource = new ExpensiveResource() try {

resource.open()

closure(resource)

}finally { resource?.close()

}

}

safeResource { it -> it.writeData('hello world!') }

The method safeResource accepts a closure and encapsulates the resource management code, leaving the calling code to show its intent much more clearly.

Groovy provides a similar approach when dealing with file access by automatically handling resource management code such as opening, closing, and handling exceptions behind the scenes. For instance, the method file.eachLineMatch(pattern) { ... } opens

a file, iterates over each line trying to find the lines that match the specified pattern, and then invokes some code passed in form of a closure for the matching line(s), if any, before eventually closing the file.

106

www.it-ebooks.info

Chapter 3

There's more...

Once you get familiar with Groovy closures, it will not take long before you'll encounter the need to curry your closures. The term currying comes from the functional programming domain, and it defines a way to apply default values to a closure by defining a complete new closure. Here is an example:

def multiply = { x, y -> x * y } multiply 3, 5

Result: 15

def closureByFour = multiply.curry(4)

The previous code snippets shows a standard closure that multiplies two numbers and returns the result (remember, no return is needed). The second closure calls the curry method on the first closure and specifies the value 4. The second closure, closureByFour, can then be invoked with one argument only, and that argument will be multiplied by 4, the value passed to the curry method. The limitation of the curry method is that arguments are bound from left to right in the argument list. In other words, in two arguments-closures, only the left argument can be used with the curry function.

The following example shows an interesting use case for currying. Let's define a closure that filters the elements of a list. The filter argument is a closure:

def filterList = { filter, list -> list.findAll(filter) }

Now let's define a couple of closures that can be used as filters:

def even = { it % 2 == 0 } def odd = { !even(it) }

Finally, we create two new closures by applying the curry function to the original closure, and passing one of the closure filters:

def evenFilterList = filterList.curry(even) def oddFilterList = filterList.curry(odd)

The new closures can now be invoked autonomously to filter out odd and even numbers:

assert [0,2,4,6,8] == evenFilterList(0..8) assert [1,3,5,7] == oddFilterList(0..8)

107

www.it-ebooks.info

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]