Java原型克隆无法正常工作?

时间:2019-01-30 17:28:55

标签: java design-patterns prototype

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;
    }
}

在这里,我想克隆一个此类的对象Swimmers。

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之后,将更改原始类的游泳者列表,复制对象swimmersCopy。

这里我只是在表格中显示原始对象的游泳者列表,我可以按其上的属性进行排序,但是每当单击默认排序按钮时,我都想按游泳者之前插入的默认顺序列出游泳者?但是应用排序也会改变克隆对象的游泳者列表吗?

3 个答案:

答案 0 :(得分:3)

clone()的默认Object方法仅进行浅克隆。正如Javadoc所说,您需要自己进行深度克隆:

  

按照惯例,此方法返回的对象应为   与此对象(正在克隆)无关。为达到这个   独立性,则可能有必要修改   super.clone返回的对象,然后再返回。通常,这   表示复制包含内部“深层”的任何可变对象   克隆对象的“结构”并替换对   这些对象以及对副本的引用。

这意味着您的原始源代码只是执行浅表复制:

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中要说的内容。

答案 1 :(得分:1)

clone()方法的默认版本创建对象的浅表副本。对象的浅表副本将具有原始对象所有字段的精确副本。如果原始对象具有对其他对象的任何引用作为字段,则仅将那些对象的引用复制到克隆对象中,而不创建这些对象的副本。这意味着通过克隆对象对这些对象所做的任何更改都将反映在原始对象中,反之亦然。浅拷贝并非与原始对象100%脱离。浅拷贝并非100%独立于原始对象。

More info

答案 2 :(得分:1)

其背后的原因是insert into job (job_id, name) values (35, 'Defend them') 方法不会复制整个对象:此处创建的副本将共享同一列表。 clone()方法的使用是危险的操作方式。例如,您可能更喜欢使用复制构造函数:

clone()

请注意,在此示例中,public Swimmers(Swimmers s) { this.swimmers = new ArrayList<Swimmer>(s.swimmers); } 的副本构造函数调用Swimmers的副本构造函数。