Java 原型克隆没有按预期工作?
Java prototype clone is not working as expected?
public abstract class SwimmersPrototype implements Cloneable {
public SwimmersPrototype clone() throws CloneNotSupportedException{
return (SwimmersPrototype)super.clone();
}
}
SwimmersPrototype.java
public class Swimmers extends SwimmersPrototype{
List<Swimmer> swimmers;
SortStrategy sortStrategy;
public Swimmers() {
swimmers = new ArrayList();
}
public List<Swimmer> sort() {
return sortStrategy.sort(swimmers);
}
@Override
public SwimmersPrototype clone() throws CloneNotSupportedException{
SwimmersPrototype swp = (Swimmers)super.clone();
return swp;
}
}
这里我想克隆这个class,游泳者的一个对象。
public class Swim extends javax.swing.JFrame {
Swimmers swimmers;
Swimmers swimmersCopy;
/**
* Creates new form Swim
*/
public Swim() {
initComponents();
swimmers = new Swimmers();
fillSwimmers();
fillTable(swimmers.getSwimmers());
jTableListener();
try {
swimmersCopy = (Swimmers)swimmers.clone();
} catch (CloneNotSupportedException ex) {
Logger.getLogger(Swim.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
调用sort后,原来的swimmers列表发生了变化class,复制对象swimmersCopy也发生了变化
这里我只是在 table 中显示原始对象的游泳者列表,我可以按 属性 对其进行排序,但是每当单击默认排序按钮时,我想按以下方式列出游泳者他们之前插入的默认顺序?但是应用排序,也会更改克隆对象的游泳者列表吗?
clone()
方法的默认版本创建对象的浅表副本。对象的浅表副本将具有原始对象所有字段的精确副本。如果原始对象有任何对其他对象的引用作为字段,则仅将这些对象的引用复制到克隆对象中,不会创建这些对象的副本。这意味着通过克隆对象对这些对象所做的任何更改都将反映在原始对象中,反之亦然。浅拷贝不是与原始对象 100% 不相交。浅拷贝不是 100% 独立于原始对象。
class Object
的默认 clone()
方法仅进行浅克隆。正如Javadoc所说,你需要自己实现深度克隆:
By convention, the object returned by this method should be
independent of this object (which is being cloned). To achieve this
independence, it may be necessary to modify one or more fields of the
object returned by super.clone before returning it. Typically, this
means copying any mutable objects that comprise the internal "deep
structure" of the object being cloned and replacing the references to
these objects with references to the copies.
这意味着您的原始源代码只是执行浅拷贝:
public class Swimmers extends SwimmersPrototype{
List<Swimmer> swimmers;
SortStrategy sortStrategy;
public Swimmers() {
swimmers = new ArrayList();
}
public List<Swimmer> sort() {
return sortStrategy.sort(swimmers);
}
@Override
public SwimmersPrototype clone() throws CloneNotSupportedException{
SwimmersPrototype swp = (Swimmers)super.clone();
return swp;
}
}
clone()
方法的上述实现使原始对象及其克隆对象的字段 swimmers
都指向同一个 List
。因此,当您通过其中一个对象更改列表时,您会通过另一个对象看到相同的更改。要执行深拷贝,你需要这样做:
@Override
public Swimmers clone() throws CloneNotSupportedException{
Swimmers swp = (Swimmers)super.clone();
swp.swimmers = new ArrayList<>(swimmers);
return swp;
}
但是,正如您在评论中所说,您不希望复制 swimmers
而是实施写时复制策略。首先,您应该意识到,如果您的 Swimmers 对象有可能被多个线程并发使用,那么在多线程应用程序中获得绝对正确的写时复制是非常棘手的。如果这不是您的问题,您只需对 sort()
方法进行以下更改:
public List<Swimmer> sort() {
swimmers = new ArrayList<>(swimmers); // Copy on write
return sortStrategy.sort(swimmers);
}
在这里,我们正在制作 swimmers
列表的副本,以确保我们不会对可能共享的列表进行排序。通过制作副本,我们知道这个对象是唯一持有我们将要修改的列表的引用的对象。
即使对象从未被克隆,上述修改也会不必要地增加列表复制的开销。为避免这种开销,您可以添加一个引用计数字段,您可以在 clone()
方法中增加该字段(并考虑在不再使用克隆对象时减少它的方法)。那么如果引用计数大于1就只需要复制列表即可。
顺便说一下,作为一般规则,在使用 Cloneable 接口之前,所有 Java 开发人员都应该阅读 Josh Bloch 在他的书 Effective Java.
中对此所说的话
这背后的原因是 clone()
方法不会复制整个对象:创建的副本将共享同一个列表。
使用 clone()
方法是一种危险的处理方式。例如,您可能更喜欢使用复制构造函数:
public Swimmers(Swimmers s) {
this.swimmers = new ArrayList<Swimmer>(s.swimmers);
}
请注意,在此示例中,Swimmers
的复制构造函数调用了 ArrayList
的复制构造函数。
public abstract class SwimmersPrototype implements Cloneable {
public SwimmersPrototype clone() throws CloneNotSupportedException{
return (SwimmersPrototype)super.clone();
}
}
SwimmersPrototype.java
public class Swimmers extends SwimmersPrototype{
List<Swimmer> swimmers;
SortStrategy sortStrategy;
public Swimmers() {
swimmers = new ArrayList();
}
public List<Swimmer> sort() {
return sortStrategy.sort(swimmers);
}
@Override
public SwimmersPrototype clone() throws CloneNotSupportedException{
SwimmersPrototype swp = (Swimmers)super.clone();
return swp;
}
}
这里我想克隆这个class,游泳者的一个对象。
public class Swim extends javax.swing.JFrame {
Swimmers swimmers;
Swimmers swimmersCopy;
/**
* Creates new form Swim
*/
public Swim() {
initComponents();
swimmers = new Swimmers();
fillSwimmers();
fillTable(swimmers.getSwimmers());
jTableListener();
try {
swimmersCopy = (Swimmers)swimmers.clone();
} catch (CloneNotSupportedException ex) {
Logger.getLogger(Swim.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
调用sort后,原来的swimmers列表发生了变化class,复制对象swimmersCopy也发生了变化
这里我只是在 table 中显示原始对象的游泳者列表,我可以按 属性 对其进行排序,但是每当单击默认排序按钮时,我想按以下方式列出游泳者他们之前插入的默认顺序?但是应用排序,也会更改克隆对象的游泳者列表吗?
clone()
方法的默认版本创建对象的浅表副本。对象的浅表副本将具有原始对象所有字段的精确副本。如果原始对象有任何对其他对象的引用作为字段,则仅将这些对象的引用复制到克隆对象中,不会创建这些对象的副本。这意味着通过克隆对象对这些对象所做的任何更改都将反映在原始对象中,反之亦然。浅拷贝不是与原始对象 100% 不相交。浅拷贝不是 100% 独立于原始对象。
class Object
的默认 clone()
方法仅进行浅克隆。正如Javadoc所说,你需要自己实现深度克隆:
By convention, the object returned by this method should be independent of this object (which is being cloned). To achieve this independence, it may be necessary to modify one or more fields of the object returned by super.clone before returning it. Typically, this means copying any mutable objects that comprise the internal "deep structure" of the object being cloned and replacing the references to these objects with references to the copies.
这意味着您的原始源代码只是执行浅拷贝:
public class Swimmers extends SwimmersPrototype{
List<Swimmer> swimmers;
SortStrategy sortStrategy;
public Swimmers() {
swimmers = new ArrayList();
}
public List<Swimmer> sort() {
return sortStrategy.sort(swimmers);
}
@Override
public SwimmersPrototype clone() throws CloneNotSupportedException{
SwimmersPrototype swp = (Swimmers)super.clone();
return swp;
}
}
clone()
方法的上述实现使原始对象及其克隆对象的字段 swimmers
都指向同一个 List
。因此,当您通过其中一个对象更改列表时,您会通过另一个对象看到相同的更改。要执行深拷贝,你需要这样做:
@Override
public Swimmers clone() throws CloneNotSupportedException{
Swimmers swp = (Swimmers)super.clone();
swp.swimmers = new ArrayList<>(swimmers);
return swp;
}
但是,正如您在评论中所说,您不希望复制 swimmers
而是实施写时复制策略。首先,您应该意识到,如果您的 Swimmers 对象有可能被多个线程并发使用,那么在多线程应用程序中获得绝对正确的写时复制是非常棘手的。如果这不是您的问题,您只需对 sort()
方法进行以下更改:
public List<Swimmer> sort() {
swimmers = new ArrayList<>(swimmers); // Copy on write
return sortStrategy.sort(swimmers);
}
在这里,我们正在制作 swimmers
列表的副本,以确保我们不会对可能共享的列表进行排序。通过制作副本,我们知道这个对象是唯一持有我们将要修改的列表的引用的对象。
即使对象从未被克隆,上述修改也会不必要地增加列表复制的开销。为避免这种开销,您可以添加一个引用计数字段,您可以在 clone()
方法中增加该字段(并考虑在不再使用克隆对象时减少它的方法)。那么如果引用计数大于1就只需要复制列表即可。
顺便说一下,作为一般规则,在使用 Cloneable 接口之前,所有 Java 开发人员都应该阅读 Josh Bloch 在他的书 Effective Java.
中对此所说的话这背后的原因是 clone()
方法不会复制整个对象:创建的副本将共享同一个列表。
使用 clone()
方法是一种危险的处理方式。例如,您可能更喜欢使用复制构造函数:
public Swimmers(Swimmers s) {
this.swimmers = new ArrayList<Swimmer>(s.swimmers);
}
请注意,在此示例中,Swimmers
的复制构造函数调用了 ArrayList
的复制构造函数。