user5460725
user5460725

Reputation:

Dealing with final fields when overriding clone

I'm writing a class in which I have to override the clone() method with the infamous "super.clone() strategy" (it's not my choice).

My code looks like this:

@Override
public myInterface clone()
{
    myClass x;
    try
    {
        x = (myClass) super.clone();
        x.row = this.row;
        x.col = this.col;
        x.color = this.color; 
        //color is a final variable, here's the error
    }
    catch(Exception e)
    {
        //not doing anything but there has to be the catch block
        //because of Cloneable issues
    }
    return x;
}

Everything would be fine, except that I can't initialize color without using a constructor, being it a final variable... is there some way to both use super.clone() AND copying final variables?

Upvotes: 8

Views: 3741

Answers (3)

Pawel Veselov
Pawel Veselov

Reputation: 4225

One thing to keep being mindful of, when mixing clone and final fields, especially the one with initializers, is that (as one of the comments rightfully says), the values are copied from the original, no matter whether that field is final or not, and initializer be damned.

So if a final field has a dynamic initializer, it's actually not executed. Which, IMHO, is breaking a serious expectation that if I have a final instance field in a class, every new instance of this class is going to initialize the field to whatever value the initializer says it should be.

Here is an example where this can be a (insert your level of seriousness) problem:


import lombok.SneakyThrows;
import java.util.HashMap;
import java.util.Map;

public class App {

    public static void main(String[] a) {

        Cloned c1 = new Cloned();
        c1.d.put("x", "x");
        Cloned c2 = c1.clone();
        System.out.println("c2.x="+c2.d.get("x"));
        c1.d.clear();
        System.out.println("c2.x="+c2.d.get("x"));

    }

    static class Cloned implements Cloneable {

        public final Map<String, String> d = new HashMap<>();

        @Override
        @SneakyThrows
        public Cloned clone() {
            return (Cloned) super.clone();
        }
    }

}

Output:

c2.x=x
c2.x=null

Upvotes: 2

Kayaman
Kayaman

Reputation: 73558

Since the call to super.clone(); will already create a (shallow) copy of all the fields, final or not, your full method will become:

@Override
public MyInterface clone() throws CloneNotSupportedException {
    return (MyInterface)super.clone();
}

This requires that the superclass also implements clone() properly (to ensure that the super.clone() eventually reaches the Object class. All the fields will be copied properly (including final ones), and if you don't require deep clones or any other special functionality, you can use this and then promise that you'll never try to implement clone() again (one of the reasons being that it's not easy to implement it correctly, as evident from this question).

Upvotes: 2

Denis Lukenich
Denis Lukenich

Reputation: 3164

Under the assumption that you don't have another choice you can use Reflection. The following code shows how this forks for the field color. You need some try-catch around it.

Field f =  getClass().getDeclaredField("color");
f.setAccessible(true);
f.set(x, this.color)

Upvotes: 1

Related Questions