Groovy Goodness: Make Class Cloneable With @AutoClone
Groovy has many AST annotations that add code to our class (the Abstract Syntax Tree - AST) before it is compiled.
So the compiled class file contains the code added by the AST annotation.
With the @AutoClone
annotation a clone
method is added and the class implements the Cloneable
interface.
We have different strategies to choose from to support cloning for our class.
The default strategy is to invoke super.clone()
in the generated clone
method.
The next statements will deep copy the properties (and optional fields) from our class.
If one of the properties cannot be cloned an exception is thrown.
In the following example code snippet we apply the @AutoClone
annotation to the classes Course
and Teacher
:
import groovy.transform.AutoClone
@AutoClone
class Course {
String name
Date date
Teacher teacher
}
@AutoClone
class Teacher {
String name
}
def mrhaki =
new Teacher(name: 'mrhaki')
def course =
new Course(
name: 'Groovy 101',
date: new Date() + 10,
teacher: mrhaki)
// We make a deep copy.
def secondCourse = course.clone()
assert secondCourse != course
assert !secondCourse.is(course)
assert secondCourse.teacher != course.teacher
// Change property on cloned instance.
secondCourse.name = 'Groovy 101 2nd edition'
assert secondCourse.name == 'Groovy 101 2nd edition'
assert course.name == 'Groovy 101'
We can use the excludes
annotation attribute to give a list of properties that must not be cloned:
import groovy.transform.AutoClone
// Do not clone the teacher property.
@AutoClone(excludes = ['teacher'])
class Course {
String name
Date date
Teacher teacher
}
@AutoClone()
class Teacher {
String name
}
def mrhaki =
new Teacher(name: 'mrhaki')
def course =
new Course(
name: 'Groovy 101',
date: new Date() + 10,
teacher: mrhaki)
// We make a deep copy.
def secondCourse = course.clone()
assert secondCourse != course
assert !secondCourse.is(course)
// Only the teacher property is
// a shallow copy.
assert secondCourse.teacher == course.teacher
// Change property on teacher property on cloned instance.
secondCourse.teacher.name = 'hubert'
assert secondCourse.teacher.name == 'hubert'
assert course.teacher.name == 'hubert'
To include fields as well we must set the annotation attribute includeFields
to true
.
If we want to invoke the default constructor of our class in the clone
method we must use the clone style AutoCloneStyle.SIMPLE
.
In the generated clone
method the constructor is invoked followed by copying the properties:
import groovy.transform.AutoClone import static groovy.transform.AutoCloneStyle.SIMPLE
@AutoClone(style = SIMPLE) class Course { String name Date date Teacher teacher
static int counter
Course() {
counter++
}
}
@AutoClone(style = SIMPLE) class Teacher { String name
static int counter
Teacher() {
counter++
}
}
def mrhaki = new Teacher(name: 'mrhaki')
def course = new Course( name: 'Groovy 101', date: new Date() + 10, teacher: mrhaki)
def otherCourse = course.clone()
// Constructor is invoked twice: // once by ourselves to create a // course, the other by the clone() // method added by @AutoClone. assert course.counter == 2 assert course.teacher.counter == 2
The last clone style we can choose is AutoCloneStyle.COPY_CONSTRUCTOR
.
This time the annotation will add a protected
constructor that takes another object of the same type as argument.
This new constructor is used in the generated clone
method.
This style is useful if we have final
read-only properties that can only be set via the constructor:
import groovy.transform.AutoClone
import static groovy.transform.AutoCloneStyle.COPY_CONSTRUCTOR
@AutoClone(style = COPY_CONSTRUCTOR)
class Course {
final String name
final Date date
final Teacher teacher
Course(
final String name,
final Date date,
final Teacher teacher) {
this.name = name
this.date = date
this.teacher = teacher
}
}
@AutoClone(style = COPY_CONSTRUCTOR)
class Teacher {
final String name
Teacher(final String name) {
this.name = name
}
}
def mrhaki =
new Teacher('mrhaki')
def course =
new Course(
'Groovy 101',
new Date() + 10,
mrhaki)
def secondCourse = course.clone()
assert secondCourse != course
assert !secondCourse.is(course)
assert secondCourse.teacher != mrhaki
assert !secondCourse.teacher.is(mrhaki)
This annotation was already available since Groovy 1.8.
Written with Groovy 2.4.6.