如何在Java中克隆具有final字段的抽象对象?

18
这个问题和这个帖子中,解释了如何通过使用受保护的复制构造函数克隆具有最终字段的对象。

然而,假设我们有:

public abstract class Person implements Cloneable
{
    private final Brain brain; // brain is final since I do not want 
                // any transplant on it once created!
    private int age;
    public Person(Brain aBrain, int theAge)
    {
        brain = aBrain; 
        age = theAge;
    }
    protected Person(Person another)
    {
        Brain refBrain = null;
        try
        {
            refBrain = (Brain) another.brain.clone();
            // You can set the brain in the constructor
        }
        catch(CloneNotSupportedException e) {}
        brain = refBrain;
        age = another.age;
    }
    public String toString()
    {
        return "This is person with " + brain;
        // Not meant to sound rude as it reads!
    }
    public Object clone()
    {
        return new Person(this);
    }

    public abstract void Think(); //!!!!
    …
}

由于我们无法实例化抽象类,因此会返回错误。我们该如何解决这个问题?


2
当你决定重写clone时,不要忘记你可以使返回类型更具体(例如Person而不是Object),并且它不需要声明throws CloneNotSupportedException(你可能应该为Brain这样做)。 - maaartinus
2
我忍不住停下来思考你的问题实际上相当于“如何克隆一个人?”而我们中没有人认为这很奇怪,因为我们都是程序员。 - Pharap
我认为这更像是一个设计问题。如果Person类的所有可能实现都未知,那么我会问自己在这种情况下使用继承是否正确。问题可以使用组合解决吗?有哪些可能的Person实现?因为如果你可以使用组合,解决方案就很容易了,只需要调用Person的构造函数并设置所有变量,否则,如果继承很重要,你知道你需要在Person中声明一个Person clone()方法,所以将其声明为抽象的,所有的实现都将决定如何实现它。希望能帮到你,加油! - rascio
4个回答

21

在抽象类中不实现 clone() 方法,只在具体子类中实现。

public class SomeConcretePerson extends Person
{
    public SomeConcretePerson (SomeConcretePerson another)
    {
        super (another); // this will invoke Person's copy constructor
    }

    public Object clone()
    {
        return new SomeConcretePerson(this);
    }
}

嗨@Eran:抽象类如何知道其子类的存在? - ΦXocę 웃 Пepeúpa ツ
3
它不知道它们的存在,也不应该知道它们的存在。 - Eran
1
好的,但是...真麻烦!那是唯一的方法吗?如果是这样,我想我应该在“Person”中声明public abstract Object clone(),对吧? - justHelloWorld
1
@justHelloWorld,你当然可以这样做,但并不是必须的。 - Eran
@OHGODSPIDERS:实现“Cloneable”的意义是什么?这里根本没有使用它。 - Holger

7
在一些罕见的情况下,我们可能无法使用复制构造函数技术,而必须使用clone()方法。对于这些情况,值得知道的是Java为final字段问题提供了一个解决方法:
public abstract class Person implements Cloneable {
    private final Brain brain;
    private int age;
    public Person(Brain aBrain, int theAge) {
        brain = aBrain; 
        age = theAge;
    }
    @Override public String toString() {
        return "This is person with " + brain;
        // Not meant to sound rude as it reads!
    }
    @Override public Person clone() {
        try {
            Person clone = (Person)super.clone();
            Field brainField=Person.class.getDeclaredField("brain");
            brainField.setAccessible(true);
            brainField.set(clone, brain.clone());
            return clone;
        } catch (CloneNotSupportedException|ReflectiveOperationException ex) {
            throw new AssertionError(ex);
        }
    }

    public abstract void think();

    …
}

“final”限制的覆盖可能性是为了这样的用例而创建的,即克隆或反序列化对象,在这种情况下不会调用构造函数。《Java语言规范,§17.5.3.修改“final”字段》指出:
在某些情况下,例如反序列化,系统将需要在构造之后更改对象的“final”字段。“final”字段可以通过反射和其他实现相关手段进行更改。这种模式中唯一具有合理语义的模式是先构造对象,然后更新对象的“final”字段。在所有对对象的“final”字段的更新完成之前,不应使对象对其他线程可见,也不应读取“final”字段。
这正是示例的工作方式,它在克隆构造之后立即设置“final”字段,然后再将克隆暴露给任何人,并且不读取任何字段。
正如所说,需要这种方法的情况很少。只要您可以实现基于复制构造函数的解决方案,就使用它。

你忘了提到这个解决方案(以及任何基于反射的反射ftm)在受影响的字段重命名时会静默中断,并且这将成为一个问题,最早不会早于运行时。 - hiergiltdiestfu
@hiergiltdiestfu:确实,尽管该领域和clone方法位于同一类中,并且审计工具应该能够通过静态代码分析检查正确性。但通常,使用反射意味着失去编译时检查... - Holger

0
如果您只想获取类的新实例而不克隆其成员的值,则可以使用以下代码:
public  static < T > T getNewInstance ( Class <T> type )
{
    try 
    {
        return type.newInstance()  ;
    } catch ( InstantiationException | IllegalAccessException e) 
    {
        e.printStackTrace();
    }
    return null ;
}

如果需要深度克隆对象,您可以使用 com.rits.cloning.Cloner 工具。例如:

private T clone(T resource){

    Cloner cloner = new Cloner();
    T cloneObject = (T) cloner.deepClone(obj);
    return cloneObject;
}

0

我们无法实例化一个抽象类,但是我们可以在子类中实现它

class Teacher extends Person {

    public Teacher(Brain aBrain, int theAge) {
        super(aBrain, theAge);
    }

    protected Teacher(Person another) {
        super(another);
    }


    public Object clone() {
        return new Teacher(this);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接