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

2022-09-02 21:01:16

这个问题这篇文章中,解释了如何使用受保护的复制构造函数克隆具有最终字段的对象。

但是,假设我们有:

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(); //!!!!
    …
}

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


答案 1

不要在抽象类中实现该方法,而只需在具体的子类中实现。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);
    }
}

答案 2

在极少数情况下,我们可能无法使用复制构造函数技术,而必须使用该方法。对于这些情况,值得一提的是,Java为现场问题提供了一个解决方法:clone()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();

    …
}

覆盖限制的可能性正是为这样的用例创建的,克隆或反序列化对象,其中不会调用构造函数。Java 语言规范,§17.5.3。最终字段的后续修改状态为:final

在某些情况下,例如反序列化,系统需要在构造后更改对象的字段。 字段可以通过反射和其他依赖于实现的方式进行更改。这具有合理语义的唯一模式是构造对象,然后更新对象的字段。在完成对对象字段的所有更新之前,不应使该对象对其他线程可见,也不应读取该字段。finalfinalfinalfinalfinal

这正是该示例的工作原理,在克隆构造之后,在克隆向任何人公开之前设置字段,并且不读取任何字段。final

如前所述,需要这样做的情况很少见。只要您可以实现基于复制构造函数的解决方案,就可以使用它。