Как клонировать абстрактные объекты с конечными полями в Java?

В этот вопрос, и этот post объясняется, как клонировать объекты с окончательным полей с помощью защищенных конструкторов копий.

Однако, полагая, что мы имеем:

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 copy constructor
    }

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

Ответ 2

В некоторых редких случаях мы, возможно, не сможем использовать технику конструктора копирования и должны использовать метод 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 прямо после построения клонов, прежде чем клон будет доступен кому-либо и не прочитает какое-либо поле.

Как сказано, случаи, когда это требуется, редки. До тех пор, пока вы можете реализовать решение на основе конструктора копий, используйте его.

Ответ 3

Если вам просто нужен новый экземпляр класса без клонирования значений его членов, вы можете использовать следующее:

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;
}

Ответ 4

Мы не можем создать экземпляр абстрактного класса, но мы можем сделать это в subClass

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);
    }
}