Java 为什么克隆方法不应该在构建中的克隆上调用任何非最终方法

Java 为什么克隆方法不应该在构建中的克隆上调用任何非最终方法,java,final,Java,Final,我在读《有效java第二版》时,偶然发现了这一段。以下行为何时发生,为什么会产生问题 与构造函数一样,克隆方法不应调用任何非最终 在建克隆的方法(第17项)。如果克隆调用 重写的方法,此方法将在中的子类之前执行 它被定义为有机会修复其在克隆中的状态, 很可能导致克隆和原始版本的损坏。 因此,前面讨论的put(key,value)方法 段落应为最终段落或非公开段落。(如果是私人的,则为 可能是非最终公共方法的“助手方法”。) 注意:我不知道重写方法a()时,父构造函数使用方法a()。当我们从子类执

我在读《有效java第二版》时,偶然发现了这一段。以下行为何时发生,为什么会产生问题

与构造函数一样,克隆方法不应调用任何非最终 在建克隆的方法(第17项)。如果克隆调用 重写的方法,此方法将在中的子类之前执行 它被定义为有机会修复其在克隆中的状态, 很可能导致克隆和原始版本的损坏。 因此,前面讨论的put(key,value)方法 段落应为最终段落或非公开段落。(如果是私人的,则为 可能是非最终公共方法的“助手方法”。)


注意:我不知道重写方法a()时,父构造函数使用方法a()。当我们从子类执行super()时,会调用new a()而不是父类的a()。我知道clone()也会出现同样的问题,这仅仅是因为
受保护的
公共的
非final方法可以被子类覆盖,这是初始化问题的根源。假设重写的方法在初始化字段之前尝试访问该字段,则会得到一个
NullPointerException
。假设它调用另一个依赖于对象特定状态的方法,而该状态在完全初始化之前无法保证,那么它将导致程序出现更微妙的错误或不正确

我想说的都已经在你的书中了,所以让我们添加一个具体的例子:

public SpaceShip {
    private double oilLevelInLitres;
    private String model;

    public SpaceShip(double oilLevelInLitres, String model) {
       this.oilLevelInLitres = oilLevelInLitres;
       displayOilLevel();
       this.model = model;
    }

    public void displayOilLevel() {
       System.out.println("Oil level is currently " + oilLevelInLitres + " litres");
    }
}

public SpaceShipWithSecondaryReservoir {    
    public SpaceShip(double oilLevelInLitres, double secondaryReservoirOilLevelInLitres, String oilLevelInLitres) {
        super(oilLevelInLitres, oilLevelInLitres);
        this.secondaryReservoirOilLevelInLitres = secondaryReservoirOilLevelInLitres;
    }

    public void displayOilLevel() {
        System.out.println("Model " + model + " oil level is currently " + oilLevelInLitres + 
            " litres and " + secondaryReservoirOilLevelInLitres + " litres in the seconday reservoir");
    }
}

public Main() {
    public static void main(String[] args) {
        // will print "Model null oil level is currently 17.0 litres and 5.0 litres in 
        // the secondary reservoir"
        new SpaceShipWithSecondaryReservoir(17, 5, "Falcon-3X"); 
    }
}
在本例中,您可能认为父类可能在调用方法之前初始化了
模型
名称,您是对的,但在编程人员编写父类构造函数的那一刻,他假设display方法不需要除油位以外的任何其他状态


这是一个编程错误,可以通过在构造函数末尾调用display方法来避免,但在更复杂的情况下,它不会那么琐碎和明显。在构造函数中调用可重写方法会使您暴露出一类错误,当您仅调用final或private方法时,这些错误并不存在。

我添加了一个示例来说明您编写的示例不涉及clone()方法。这个原则以何种方式应用于clone()方法?如果我们按照书中的建议做,我们应该使用super.clone()创建一个临时克隆,然后使用适当的setter方法进行调整。如果我们在clone()方法中调用重写的方法,它们将在子类clone=(SubClass)super.clone()返回的对象上被调用。在前面的语句之后,对象应该已经处于稳定状态。我们如何制作一个克隆中使用的重写方法导致不稳定状态的示例?谢谢:)你可以使用与我的答案完全相同的技巧。您对
Cloneable
类进行子类化,在子类中除了超类的字段之外添加一个字段,从父类的
clone
方法调用一个可重写方法,并在子类中重写该方法,以使用仅存在于子类中的字段。由于父类不知道该字段,因此在调用可重写方法之前无法设置它,因此该方法将因NPE而失败。您能看看这段代码并看看有什么问题吗?我不能抛出NPE。我对你的要点发表了评论:)我做到了,它确实起了作用。但是,NPE是否与克隆方法和覆盖相关?我的意思是,如果你添加那一行,即使你没有克隆任何对象,即使你没有覆盖任何方法,你也有NPE。您只是在初始化之前使用了一些东西,这可能发生在代码中的任何一点上,而不涉及克隆和重写。