Как клонировать абстрактные объекты с конечными полями в 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);
}
}