无克隆方法¶
ID: java/missing-clone-method
Kind: problem
Security severity:
Severity: error
Precision: medium
Tags:
- reliability
- maintainability
Query suites:
- java-security-and-quality.qls
实现 Cloneable
的类应覆盖 Object.clone
。对于非平凡对象,Cloneable
合约要求对对象的内部状态进行深度复制。没有 clone
方法的类表示该类违反了合约,并且将产生不良行为。
Java API 规范指出,对于对象 x
,clone
方法的一般目的是满足以下三个属性
x.clone() != x
(克隆对象是不同的对象实例)x.clone().getClass() == x.getClass()
(克隆对象与源对象类型相同)x.clone().equals(x)
(克隆对象与源对象具有相同的“内容”)为了使克隆对象与源对象具有相同的类型,非 final 类必须调用super.clone
,并且该调用最终必须到达Object.clone
,它会创建一个正确类型的实例。如果它使用构造函数创建一个新对象,则未实现clone
方法的子类将返回错误类型的对象。此外,所有也重写clone
的类的超类都必须调用super.clone
。否则,它永远不会到达Object.clone
,并且会创建一个不正确的类型对象。
但是,由于 Object.clone
只对对象字段进行浅层复制,因此任何具有“深层结构”(例如,使用数组或 Collection
的对象)的 Cloneable
对象都必须获取由对 super.clone
的调用产生的克隆,并将显式创建的结构副本分配给克隆的字段。这意味着克隆实例不与其源对象共享其内部状态。如果它确实共享其内部状态,则在克隆对象中进行的任何更改也会影响源对象的内部状态,这可能会导致意外的行为。
一个额外的复杂之处在于,clone
无法修改 final 字段中的值,而这些值已经由对 super.clone
的调用设置。必须将某些字段设为非 final,才能正确实现 clone
方法。
建议¶
创建对象内部状态的深度副本的必要性意味着,对于大多数对象而言,clone
必须被重写以满足 Cloneable
契约。实现一个 clone
方法,正确创建克隆对象的内部状态。
此建议的显着例外是
仅包含基本类型的类(只要其
Cloneable
超类型全部调用super.clone
,Object.clone
就会正确克隆这些类型)。不引入新状态的
Cloneable
类的子类。
示例¶
在以下示例中,WrongStack
未实现 clone
。这意味着当 ws1clone
从 ws1
克隆时,将使用默认的 clone
实现。这会导致对 ws1clone
栈的操作影响 ws1
栈。
abstract class AbstractStack implements Cloneable {
public AbstractStack clone() {
try {
return (AbstractStack) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError("Should not happen");
}
}
}
class WrongStack extends AbstractStack {
private static final int MAX_STACK = 10;
int[] elements = new int[MAX_STACK];
int top = -1;
void push(int newInt) {
elements[++top] = newInt;
}
int pop() {
return elements[top--];
}
// BAD: No 'clone' method to create a copy of the elements.
// Therefore, the default 'clone' implementation (shallow copy) is used, which
// is equivalent to:
//
// public WrongStack clone() {
// WrongStack cloned = (WrongStack) super.clone();
// cloned.elements = elements; // Both 'this' and 'cloned' now use the same elements.
// return cloned;
// }
}
public class MissingMethodClone {
public static void main(String[] args) {
WrongStack ws1 = new WrongStack(); // ws1: {}
ws1.push(1); // ws1: {1}
ws1.push(2); // ws1: {1,2}
WrongStack ws1clone = (WrongStack) ws1.clone(); // ws1clone: {1,2}
ws1clone.pop(); // ws1clone: {1}
ws1clone.push(3); // ws1clone: {1,3}
System.out.println(ws1.pop()); // Because ws1 and ws1clone have the same
// elements, this prints 3 instead of 2
}
}
在以下修改后的示例中,RightStack
确实实现了 clone
。这意味着当 rs1clone
从 rs1
克隆时,对 rs1clone
栈的操作不会影响 rs1
栈。
abstract class AbstractStack implements Cloneable {
public AbstractStack clone() {
try {
return (AbstractStack) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError("Should not happen");
}
}
}
class RightStack extends AbstractStack {
private static final int MAX_STACK = 10;
int[] elements = new int[MAX_STACK];
int top = -1;
void push(int newInt) {
elements[++top] = newInt;
}
int pop() {
return elements[top--];
}
// GOOD: 'clone' method to create a copy of the elements.
public RightStack clone() {
RightStack cloned = (RightStack) super.clone();
cloned.elements = elements.clone(); // 'cloned' has its own elements.
return cloned;
}
}
public class MissingMethodClone {
public static void main(String[] args) {
RightStack rs1 = new RightStack(); // rs1: {}
rs1.push(1); // rs1: {1}
rs1.push(2); // rs1: {1,2}
RightStack rs1clone = rs1.clone(); // rs1clone: {1,2}
rs1clone.pop(); // rs1clone: {1}
rs1clone.push(3); // rs1clone: {1,3}
System.out.println(rs1.pop()); // Correctly prints 2
}
}
参考¶
J. Bloch,Effective Java(第二版),第 11 项。Addison-Wesley,2008 年。
Java API 规范:Object.clone()。