4

I am deep-cloning instances of a certain class by implementing clone() in this class and in its whole hierarchy of fields that compose it. In the clone() implementations that I put in these classes, I assign each field of the new instance by calling clone() on the corresponding field of the original (this). Then I just call clone() on the main class. I believe this is quite a standard way of deep-cloning.

Below there is a small runnable example. The clone I get is a real deep copy, where the objects contained in each field and subfield are all new objects, identical to their counterparts from the original instance.

But this means that if in the original the fields a and b were referencing the same object X, in the deep clone they will not be referencing the same object (clone of X); instead they will be referencing two different clones of X.

So I would like to deep-clone an object by deep-cloning all its fields in its whole hierarchy, but if the hierarchy contains the same reference in more than one field, only one of these fields should be deep-cloned into a new object; the other fields will just reference this new object.

It doesn't look like a problem with an easy solution, however I'm wondering if some technique exists for that, or maybe some tool or library that does that.

TestClone.java

public class TestClone {

    public static void main(String[] args) throws CloneNotSupportedException {

        // Create the object to share :

        SharedObject shared = new SharedObject(1);

        // Create the object to clone, which will own two Holder instances
        // both holding a reference to *the same* object :

        MainObject original = new MainObject(new Holder(shared), new Holder(shared));

        // Show that both holders hold a reference to the same object :

        System.out.println("Original holder1 holds " + original.holder1.field.hashCode());
        System.out.println("Original holder2 holds " + original.holder2.field.hashCode());

        // Deep-clone the main object :

        MainObject cloned = (MainObject) original.clone();

        // Show that the two cloned holders now hold a reference to *different* cloned objects :

        System.err.println("Cloned   holder1 holds " + cloned.holder1.field.hashCode());
        System.err.println("Cloned   holder2 holds " + cloned.holder2.field.hashCode());

        // How to clone so that they will hold a reference to *the same* cloned object ?
    }

}

SharedObject.java

public class SharedObject implements Cloneable {

    public int n;

    public SharedObject(int n) {

        this.n = n;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {

        SharedObject clone = (SharedObject) super.clone();

        clone.n = this.n;

        return clone;
    }

}

Holder.java

public class Holder implements Cloneable {

    public SharedObject field;

    public Holder(SharedObject field) {

        this.field = field;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {

        Holder clone = (Holder) super.clone();

        clone.field = (SharedObject) this.field.clone();

        return clone;
    }

}

MainObject.java

public class MainObject implements Cloneable {

    public Holder holder1;

    public Holder holder2;

    public MainObject(Holder holder1, Holder holder2) {

        this.holder1 = holder1;

        this.holder2 = holder2;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {

        MainObject clone = (MainObject) super.clone();

        clone.holder1 = (Holder) this.holder1.clone();

        clone.holder2 = (Holder) this.holder2.clone();

        return clone;
    }   

}
SantiBailors
  • 1,596
  • 3
  • 21
  • 44

2 Answers2

2

There is no "standard" way of such a clone operation. Additionally, I am not aware of any library that supports that.

What your requirement really says, is to build a 1-to-1 mapping (a bijection) from original object to a cloned object.

A technique would be to first build up such a hierarchy by cloning each object if it is not in the map, once the clone method is called on your root object. Afterwards, assemble the references in the new clone hierarchy.

That technique - by the way - is already implemented by the serialization technique in Java. Have all your classes implement Serializable, then write the root object to an ObjectOutputStream, pipe it to an ObjectInputStream, and then deserialize all objects. The serializing mechanism takes care of your requirement.

Community
  • 1
  • 1
Seelenvirtuose
  • 20,273
  • 6
  • 37
  • 66
  • I modified the test code by having `SharedObject`, `Holder` and `MainObject` all implement `Serializable`, I removed all the `clone()` implementations, and in `TestClone` I clone by just calling `SerializationUtils.clone(MainObject)` . When I run it it shows that now the two "holders" in the clone hold a reference to the same _cloned_ `SharedObject` instance. Exactly what I needed. So it seems that with this solution I'm on the right track. I will now apply this to the real code where the class to clone is of course much bigger and more complex and I will see if I run into some issue. – SantiBailors Aug 28 '16 at 17:18
  • Well, glad to help you. – Seelenvirtuose Aug 28 '16 at 19:37
  • I implemented your solution in the real code and it worked great. Thanks a lot, especially for mentioning that unlike other techniques this one doesn't just deep-clone but it also makes the shared references shared in the clone too. I had read about serialization and in particular `SerializationUtils.clone` being an option for cloning but nowhere did I see mentioned this vital difference with other cloning techniques, which I find surprising. – SantiBailors Aug 29 '16 at 08:13
1

Short answer: not possible.

Long answer:

That is the thing with deep cloning: you don't have good access to it. You see, in the end, cloning is done by the JVM (more or less: black magic).

Now you ask: how can I use that "standard" way of cloning; but somehow makes sure that the whole process of cloning some some "root object X" will use some sort of "caching" mechanism. I am note aware of any existing mechanism to instruct the JVM to do so.

Long story short: I think you have to look into other alternatives; like serializing into some reasonable format; to then work with that output.

And to finally a quote from a great answer:

It sounds like you're thinking that cloning is a good idea (vs. using a copy constructor, factories or their equivalent).

And, continued with:

Now, more importantly, cloning is a bad idea.

Community
  • 1
  • 1
GhostCat
  • 137,827
  • 25
  • 176
  • 248
  • 1
    Really, cloning is a powerful technique and has its usefulness. It is maybe an advanced feature and could be harmful if not used properly. – Seelenvirtuose Aug 28 '16 at 13:14
  • In my understanding copy constructors have the same problem. In my copy constructor I would assign to each mutable field a `new` instance. So no two fields in the clone will ever hold the same reference, but in the original they might. Indeed I can't see how even factories or other techniques might produce an object that reflects the original in the way I need, unless the technique includes checking if in the original the field to clone holds a reference that is also held by another field in the hierarchy. I hope serialization does that, thanks for mentioning it, I'll try. – SantiBailors Aug 28 '16 at 14:27