【问题标题】:How to clone abstract objects with final fields in Java?如何在 Java 中克隆具有最终字段的抽象对象?
【发布时间】:2017-11-16 07:16:37
【问题描述】:

this 问题和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(); //!!!!
    …
}

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

【问题讨论】:

  • 当你费心重写clone时,别忘了你可以让返回类型更具体(Person而不是Object)并且它不需要声明@ 987654327@(您可能应该为Brain 这样做)。
  • 我忍不住停下来想你的问题实际上相当于“我如何克隆一个人?”我们没有人认为这很奇怪,因为我们是程序员。
  • 我觉得更多的是设计问题。如果 Person 类的所有可能实现都是未知的,我会问自己在这种情况下使用继承是否正确。可以使用组合来解决问题吗?哪个是 Person 的可能实现?因为如果可以使用组合,解决方法很简单,就是调用Person的构造函数并设置所有变量,否则,如果继承很重要,你知道你需要Person中的Person clone()方法,所以声明它是抽象的,所有的实现都将决定如何实现它。希望对您有所帮助,干杯!

标签: java abstract-class clone


【解决方案1】:

在极少数情况下,我们可能无法使用复制构造函数技术,而必须使用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 字段。在对象的final 字段的所有更新完成之前,不应使该对象对其他线程可见,也不应读取final 字段。

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

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

【讨论】:

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

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

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

【讨论】:

    【解决方案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】:

      您不在抽象类中实现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ツ 它没有意识到它们,也不应该意识到它们。
      • 好的,但是....多么痛苦!这是唯一的方法吗?在那种情况下,我想我应该在Person 中声明public abstract Object clone(),对吧?
      • @justHelloWorld 你当然可以,但你不必这样做。
      • @OHGODSPIDERS:实现Cloneable 的意义何在?这里根本不用。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-14
      • 1970-01-01
      相关资源
      最近更新 更多