如何用Java中的最终字段克隆抽象对象?

问题描述:

this问题和这post是解释如何通过使用受保护的拷贝构造函数克隆与最终字段的对象。如何用Java中的最终字段克隆抽象对象?

然而,假设我们有:

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'),并且它不需要声明'抛出CloneNotSupportedException'(你应该可以为'Brain'做这个)。 – maaartinus

+2

我不禁停下来,认为你的问题实际上等于“我如何克隆一个人?”没有人认为这很奇怪,因为我们是程序员。 – Pharap

+0

我认为这更多是一个设计问题。如果所有可能的Person类的实现都是未知的,我会问自己在这种情况下使用继承是否正确。 问题可以使用组合来解决吗? Person是一个可能的实现?因为如果你可以使用组合,解决方案很简单,它只是调用Person的构造函数并设置所有变量,否则,如果继承很重要,那么你知道在Person中需要一个'Person clone()'方法,所以声明它是抽象的,所有的实现将决定如何实现它。希望它有帮助,欢呼! – rascio

您不在抽象类中实现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); 
    } 
} 
+0

hi @Eran:抽象类如何意识到它的子类的存在? –

+2

@ΦXocę웃Пepeúpaツ它不知道它们,也不应该意识到它们。 – Eran

+1

好吧,但......真是一种痛苦!这是唯一的方法吗?在这种情况下,我想我应该在'Person'中声明'public abstract Object clone()',对吧? – justHelloWorld

如果你只是想在类的新实例,而不克隆其成员的值,那么你可以使用以下命令:

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

我们不能实例化一个抽象类,但我们可以在子类

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

在某些罕见的情况下做到这一点,我们可能无法使用拷贝构造技术和有使用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 Language Specification, §17.5.3. Subsequent Modification of final Fields状态:

在某些情况下,如反序列化时,系统将需要改变施工后对象的final领域。 final字段可以通过反射和其他实现相关手段进行更改。其中有合理的语义的唯一模式是在其中构建对象,然后更新对象的字段。对象不应该对其他线程可见,也不应该读取对象字段,直到完成对对象的所有字段的更新。

这正是该示例的工作原理,在克隆构建后,在将克隆暴露给任何人之前设置final字段,并且不读取任何字段。

如上所述,这是必需的情况,很少见。只要你可以实现一个基于拷贝构造函数的解决方案,就可以使用它。

+0

您忘记提及此解决方案(以及基于反射ftm的任何反射)*无论何时重新命名受影响的字段都会自动断开,并且这将成为不早于运行时间的问题。 – hiergiltdiestfu

+0

@hiergiltdiestfu:的确,尽管字段和clone方法驻留在同一个类中,并且审计工具应该能够通过静态代码分析来检查正确性。但通常情况下,使用Reflection意味着失去编译时检查... – Holger