Reputation: 4402
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
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:
B
InheritedB
, which has a separate val
fieldThe 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
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
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
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
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