无克隆方法¶
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()。