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.
The following steps will show the power of the @AutoClone
annotation:
Vehicle
and annotate the class with the @AutoClone
annotation:import groovy.transform.AutoClone @AutoClone class Vehicle { String brand String type Long wheelsNumber }
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.
class Engine { int horseEngine Number liter }
engine
property to the Vehicle
class:Engine engine
def v1 = new Vehicle( brand: 'Ferrari', type: 'Testarossa', wheelsNumber: 4 ) def e1 = new Engine( horseEngine: 390, 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.literv2.engine.liter = 8 println 'Original vehicle engine liters: ' + v1.engine.liter
Original vehicle engine liters: 4.9 Cloned vehicle engine liters: 4.9 Original vehicle engine liters: 8
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 } }
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.
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); } }
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
.