raju raju - 3 months ago 13
Java Question

why should a clone method not invoke any nonfinal methods on the clone under construction

I was reading effective java 2nd edition and came across this paragraph. When does the following action arise and why dos it create problem?


Like a constructor, a clone method should not invoke any nonfinal
methods on the clone under construction (Item 17). If clone invokes an
overridden method, this method will execute before the subclass in
which it is defined has had a chance to fix its state in the clone,
quite possibly leading to corruption in the clone and the original.
Therefore the put(key, value) method discussed in the previous
paragraph should be either final or private. (If it is private, it is
presumably the “helper method” for a nonfinal public method.)


Note: I didn't know that when we override a method a() and parent contructor used method a(). When we do super() from subclass new a() is called instead of parent's a(). I understand same problem happens with clone()

Answer

Simply because a protected or public non-final method is overridable by a subclass, which is the footstep to initialization problems. Say the overriden method tries to access a field before it gets initialized, you would get a NullPointerException. Say it calls another method that relies on a specific state of the object that is not guaranteed before full initialization, it would result in an even more subtle error or incorrectness of the program.

All I'm saying is already in your book though, so let's add a concrete example:

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

In this example you could argue that the parent class could have initialize the model name before calling the method, and you're right, but at the moment the programer wrote the parent class constructor he was making the assumption that the display method would not need any other state than the oil level.

This is a programming error that was avoidable by calling thhe display method at the end of the constructor, but in more complicated cases it would not be that trivial and obvious. Calling an overridable method in a constructor exposes you to a class of mistakes which does not exist when you only call final or private methods.