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

Chapter 3

The @Canonical annotation has only two parameters, includes and excludes, to select which properties to include or exclude in the computation. Since @Canonical doesn't take any additional parameters and uses some sensible defaults, how can we customize certain aspects of the code generation? For example, if you want to use @Canonical but customize the @ToString behavior, then annotate the class with both @Canonical and @ToString. The @ToString definition and parameters take precedence over @Canonical.

@Canonical

@ToString(excludes='age') class Person {

String name int age

}

Inheriting constructors in Groovy classes

In Java, class inheritance doesn't support the inheriting of constructors for a number of good reasons (leaving the details of constructing an object to the programmer is generally a good idea). There are times when automatically inheriting the constructors of a class would be really useful and make the code less verbose. One of these cases is when inheriting from a class that extends Exception, where all the constructors are just calling super. Groovy has a convenient annotation for doing just that, @InheritConstructors.

In this recipe, we will explore how to use this annotation.

How to do it...

Let's demonstrate the features that the @InheritConstructors annotation gives:

1.Create an Exception class: one of the classes that are used to communicate that something is horribly wrong with the code or the data:

class BusinessException extends Exception {

}

2.Try to instantiate the class using one of the default constructors of the Exception class, for instance:

def be = new BusinessException('missing resource')

The code fails at runtime with the following error:

Could not find matching constructor for: BusinessException(java.lang.String)

99

www.it-ebooks.info

Using Groovy Language Features

3.Add the groovy.transform.InheritConstructors annotation to the

BusinessException and try to instantiate the class again with a String message. This time, the code will execute without errors:

@groovy.transform.InheritConstructors

class BusinessException extends Exception {

}

How it works...

The @InheritConstructors annotation removes the boilerplate of writing matching constructors for a superclass. By adding the @InheritConstructors annotation to the class, we can create the BusinessException by using any of the constructors exposed by

java.lang.Exception:

import groovy.transform.InheritConstructors

@InheritConstructors

class BusinessException extends Exception { }

assert new BusinessException('hello').message == 'hello'

def

b1

=

new BusinessException('missing resource')

def

b2

=

new BusinessException('catastrophic failure', b1)

assert

b2.cause.message == 'missing resource'

The annotation is smart enough to detect your existing constructors, if any, and avoid overriding them during the constructor generation phase.

Adding the cloning functionality to Groovy Beans

There are several strategies to clone an object in Java. To clone an object means the ability to create an object with the same state as the original object.

A widely used strategy to clone an object is for the class to be cloned to implement the Cloneable interface, and implement a method, clone, in which the cloning code is executed. Naturally, Groovy supports this semantic but makes it even easier to implement with the @AutoClone annotation, which will be demonstrated in this recipe.

100

www.it-ebooks.info

Chapter 3

How to do it...

The following steps will show the power of the @AutoClone annotation:

1.Let's define an object Vehicle and annotate the class with the @AutoClone annotation:

import groovy.transform.AutoClone

@AutoClone

class Vehicle { String brand String type

Long wheelsNumber

}

2.In the same script where the Vehicle object is defined, add the following code:

def v1 = new Vehicle() v1.brand = 'Ferrari' v1.type = 'Testarossa' v1.wheelsNumber = 4 def v2 = v1.clone()

assert v1 instanceof Cloneable assert v1.brand == v2.brand

The script should execute without errors.

3.Let's add a second object to the mix:

class Engine { int horseEngine Number liter

}

4.Let's add the engine property to the Vehicle class:

Engine engine

5.Modify the test code, so that the vehicle is created with an engine:

def v1 = new Vehicle(

brand: 'Ferrari', type: 'Testarossa', wheelsNumber: 4

)

def e1 = new Engine( horseEngine: 390,

101

www.it-ebooks.info

Using Groovy Language Features

liter: 4.9

)

v1.engine = e1 // assign engine to car def v2 = v1.clone() // clone

println 'Original vehicle engine liters: ' + v1.engine.liter println 'Cloned vehicle engine liters: ' + v2.engine.liter v2.engine.liter = 8

println 'Original vehicle engine liters: ' + v1.engine.liter

6.The result of the modified script yields:

Original vehicle engine liters: 4.9 Cloned vehicle engine liters: 4.9 Original vehicle engine liters: 8

How it works...

The snippet in step 2 shows how easy is to clone an object without having to implement any interface. The default cloning strategy implemented by the annotation will call super.clone before calling clone on each Cloneable property of the class. If a field or property is not cloneable, it is simply copied in a bitwise fashion. If some properties don't support cloning, a

CloneNotSupportedException is thrown.

The decompiled Vehicle class has the following aspect (the code is cleaned up a bit for the sake of readability, as the Groovy-generated bytecode uses reflection heavily):

public class Vehicle implements Cloneable, GroovyObject {

public Object clone() throws CloneNotSupportedException { Object result = super.clone()

if (brand instanceof Cloneable) { result.brand = brand.clone()

}

if (type instanceof Cloneable) { result.type = type.clone()

}

if (wheelsNumber instanceof Cloneable) { result.wheelsNumber = wheelsNumber.clone()

}

return result

}

}

102

www.it-ebooks.info

Chapter 3

In step 6, we can observe that the cloning process didn't quite work as expected. An attribute of the cloned object is modified, and the same attribute on the original object gets modified as well. Not surprisingly, the reason for this behavior is that the Engine class is not annotated with the @AutoClone annotation; therefore, the cloning process only clones the reference

to the object, not the actual object. Simply annotate the Engine object with @AutoClone to have the actual object cloned.

There's more...

The @AutoClone annotation supports some additional cloning styles. The first cloning style is based on the Copy Constructor pattern:

import groovy.transform.*

@AutoClone(style=AutoCloneStyle.COPY_CONSTRUCTOR) class Vehicle {

...

}

The clone method implementation is moved into the body of a constructor that takes a parameter of the same type as the class. Then, calls to clone simply return the result of calling this constructor. Enabling the COPY_CONSTRUCTOR pattern produces Java code similar to the following code snippet:

public class Vehicle implements Cloneable {

private String brand; private String type; private Long wheelsNumber;

protected Vehicle(Vehicle other) { brand = other.brand;

type = other.type;

wheelsNumber = other.wheelsNumber;

}

public Object clone() throws CloneNotSupportedException { return new Vehicle(this);

}

}

103

www.it-ebooks.info

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