Search code examples
javaclonedeep-copy

Deep clone by collecting the fields


I have classes which aim to contain many attributes of different types. I would want to automatically deep clone all of them rather than writing an instruction for each of them:

class AttributesContainer implements Cloneable {
    Type1 a1 = new Type1(...), a2 = new Type1(...);
    Type2 b1 = new Type2(...);
    ...

    public AttributesContainer clone() {
        AttributesContainer ac = (AttributesContainer) super.clone();

        // Regroup that like using a loop
        ac.a1 = a1.clone();
        ac.a2 = a2.clone();
        ac.b1 = b1.clone();
        ...

        return ac;
    }
}

I have thought about adding the fields in a table each time but I could not change the reference of the fields:

class ContainerAbstract implements Cloneable {
    public <T> T add(T t) {
        // adds the reference into a list
        return t;
    }

    public ContainerAbstract clone() {
        ContainerAbstract ca = (ContainerAbstract) super.clone();

        // copy the attributes

        return ca;
    }
}

class AttributesContainer extends ContainerAbstract implements Cloneable {
    Type1 a1 = add(new Type1(...)), a2 = add(new Type1(...));
    Type2 b1 = add(new Type2(...));
    ...

    public AttributesContainer clone() {
        AttributesContainer ac = (AttributesContainer) super.clone();
        return ac;
    }
}

I have also thought that I could return a wrapper in the add() method but it would introduce an extra method get() to call each time I want to access an attribute:

AttributesContainer ac = new AttributesContainer();
ac.get()...;

It there a way to change the fields to there source, like we could achieve that in C using a pointer?

NB: I already checked Copy fields between similar classes in java, How do you make a deep copy of an object in Java? and http://www.java2s.com/Tutorial/Java/0125__Reflection/Returnalistofallfieldswhateveraccessstatusandonwhateversuperclasstheyweredefinedthatcanbefoundonthisclass.htm.

EDIT: One of the reasons I don't use the serialization is that in fact, I have a final property for which I just want a fresh new instance.

I thought about making it transient and then giving it a new object but I can't, since it is final:

class A {
    private Double d = new Double(2);
    public final transient B b = new B();

    public A copy() {
        A a = (A) DeepCopy.copy(this);
        a.b = new B(); // Error, b is final
        return a;
    }
}

Solution

  • Finally, the only answer I found without needing to change a final attribute is to change in the class DeepCopy in http://javatechniques.com/blog/faster-deep-copies-of-java-objects/ the ObjectOutputStream method to replace attributes of the class Type with new ones.

    ObjectOutputStream out = new ObjectOutputStream(fbos) {
        {
            enableReplaceObject(true);
        }
    
        @Override
        protected Object replaceObject(Object arg0) throws IOException {
            // TODO Auto-generated method stub
            if(arg0 instanceof Type) {
                for(Constructor<?> constructor : arg0.getClass().getConstructors()) {
                    Class<?>[] parameterTypes = constructor.getParameterTypes();
                    if(parameterTypes.length == 0 /* Number of arguments in the constructor of new Type(...) */)
                        try {
                            return constructor.newInstance(/* Arguments to the constructor */);
                        } catch (InstantiationException | IllegalAccessException
                                | IllegalArgumentException
                                | InvocationTargetException e) {
                            e.printStackTrace();
                        }
                }
    
                try {
                    throw new Exception("The constructor needed to create a new Type was not found.");
                } catch(Exception e) {
                    e.printStackTrace();
                }
    
                return null;
    
            } else {
                return super.replaceObject(arg0);
            }
        }
    };
    out.writeObject(orig);
    out.flush();
    out.close();