Different member behaviour of inner class if inner class extends outer class?

Today I stumbled about some strange inner (non-static) class behaviour.

If I have the following classes ...

class B {
    String val = "old";

    void run(){
        val = "new";
        System.out.println(val);        // outputs: new
        new InnerB().printVal();        // outputs: new
    }

    private class InnerB {
        void printVal(){ System.out.println(val); }
    }
}

new B().run();

... everything seems to be clear. The instance of InnerB belongs to the instance of B so if it should output val it prints the already replaced value 'new'.

BUT if the inner class extends the outer class this doesn't work.

class B {
    String val = "old";

    void run(){
        val = "new";
        System.out.println(val);        // outputs: new
        new InnerB().printVal();        // outputs: new
        new InheritedB().printVal();    // outputs: old new
    }

    private class InnerB {
        void printVal(){ System.out.println(val); }
    }

    private class InheritedB extends B{
        void printVal(){ System.out.println(val + " "+ B.this.val); }
    }
}

new B().run(); // outputs: new new old!

If I have a look at the constructors I also see that a new instance of B will be created if instance of InheritedB is created.

I find this very strange... Can somebody explain why there is this difference?

Jon Skeet
people
quotationmark

This line:

new InheritedB().printVal();   

creates a new instance of InheritedB, whose containing instance is the existing instance of B (where val is "new"). But at this point there are two val variables:

  • The one in the existing instance of B
  • The one in the instance of InheritedB, which has a separate val field

The value of the second variable is "old" because that's effectively the default value of the field.

This statement in InheritedB:

System.out.println(val + " "+ B.this.val);

prints out the value of val inherited from B, followed by the value of val in the "containing instance".

It might be simpler to think of it being refactored to:

public class B
{
    String val = "old";
}

public class InheritedB extends B {
    B other;

    public InheritedB(B other)
    {
        this.other = other;
    }

    void printVal() {
        System.out.println(val + " "+ other.val);
    }
}

Then you're basically running:

B original = new B();
original.val = "new":
InheritedB inherited = new InheritedB(original);
inherited.printVal();

Hopefully you can follow exactly what's going on there. The compiler is roughly performing your original code into that code.

people

See more on this question at Stackoverflow