This rule raises an issue when a class implements the interface java.lang.Cloneable
, but does not override the
Object.clone()
method.
Why is this an issue?
Cloneable
is a marker interface that defines the contract of the Object.clone
method, which is to create a
consistent copy of the instance. The clone
method is not defined by the interface though, but by class Objects
.
The general problem with marker interfaces is that their definitions cannot be enforced by the compiler because they have no own API. When a class
implements Cloneable
but does not override Object.clone
, it is highly likely that it violates the contract for
Cloneable
.
How to fix it
Consider the following example:
class Foo implements Cloneable { // Noncompliant, override `clone` method
public int value;
}
Override the clone
method in class Foo
. By convention, it must call super.clone()
. At this point, we know
that:
- By behavioral contract,
Object.clone
will not throw a CloneNotSupportedException
, because Foo
implements
Cloneable
.
- The returned object is an instance of class
Foo
We can narrow down the return type of clone
to Foo
and handle the CloneNotSupportedException
inside the
function instead of throwing it:
class Foo implements Cloneable { // Compliant
public int value;
@Override
public Foo clone() {
try {
return (Foo) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
Be aware that super.clone()
returns a one-by-one copy of the fields of the original instance. This means that in our example, the
Foo.value
field is not required to be explicitly copied in the overridden function.
If you require another copy behavior for some or all of the fields, for example, deep copy or certain invariants that need to be true for a field,
these fields must be patched after super.clone()
:
class Entity implements Cloneable {
public int id; // unique per instance
public List<Entity> children; // deep copy wanted
@Override
public Entity clone() {
try {
Entity copy = (Entity) super.clone();
copy.id = System.identityHashCode(this);
copy.children = children.stream().map(Entity::clone).toList();
return copy;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
Be aware that the Cloneable
/ Object.clone
approach has several drawbacks. You might, therefore, also consider resorting
to other solutions, such as a custom copy
method or a copy constructor:
class Entity implements Cloneable {
public int id; // unique per instance
public List<Entity> children; // deep copy wanted
Entity(Entity template) {
id = System.identityHashCode(this);
children = template.children.stream().map(Entity::new).toList();
}
}
Resources
Documentation