Marcel
Marcel

Reputation: 4402

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?

Upvotes: 24

Views: 983

Answers (5)

Jon Skeet
Jon Skeet

Reputation: 1503869

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.

Upvotes: 26

rajuGT
rajuGT

Reputation: 6414

The difference is class InnerB does not have member val in it. where as class InheritedB extends class B and has its own copy of the val member.

void run(){

    val = "new";     //<--- modifies B's val not InheritedB's val

    System.out.println(val);        // outputs: new
    new InnerB().printVal();        // outputs: new
    new InheritedB().printVal();    // outputs: old new
}

In the above code block, InnerB's printVal access the container's val member, which value was already modified in the run method to value new.

But the copy of val in InheritedB's object is still "old" value, not modified, and printVal function uses that value.

Upvotes: 2

Henry
Henry

Reputation: 43798

In the case of InheritedB there are two variables called val, the one of B and the one of InheritedB. Applying visibility rules gives the observed result.

Upvotes: 4

Arnaud
Arnaud

Reputation: 17534

Since InheritedB extends B, creating an instance of InheritedB grants it a val attribute, which is "old" by default for any new B class or subclass instance.

Here, InheritedB prints its own val attribute, not the one of the enclosing B instance.

Upvotes: 5

SLaks
SLaks

Reputation: 888223

val in InheritedB refers to the val from its base class (super.val), since that's part of this.

If you don't inherit from the outer class, val refers to the scope from the outer class (B.this.scope). However, since you inherit, this is closer in scope, and therefore hides the outer scope.

Since you never called run() on the inner this, this.val is still 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.

Yes; creating a derived class will always create an instance of its base class. There is no way to inherit from an existing instance.

Upvotes: 9

Related Questions