I was solving some exercises to understand better how inner classes in java work. I found one quite interesting exercise. The condition of the exercise is to make printName()
print "sout" instead of "main" with minimum changes. There is its code:
public class Solution {
private String name;
Solution(String name) {
this.name = name;
}
private String getName() {
return name;
}
private void sout() {
new Solution("sout") {
void printName() {
System.out.println(getName());
// the line above is an equivalent to:
// System.out.println(Solution.this.getName);
}
}.printName();
}
public static void main(String[] args) {
new Solution("main").sout();
}
}
We've got an amusing situation - the two classes have is-A and has-A connections.
It means that the anonymous inner class extends the outer class and also objects of the inner class have references to the objects of the outer class.
If you run the code above, "main" will be printed. The child cannot invoke getName()
of the parent through inheritance. But the child being inner class uses reference to parent(outer class) to access the method.
The simplest way to solve this task is to change the access modifier of getName()
from private
to anything else. So the child is able to use getName()
through inheritance and thanks to late binding "sout" will be printed.
The another way to solve this task is to use super.getName()
.
private void sout() {
new Solution("sout") {
void printName() {
System.out.println(super.getName());
}
}.printName();
}
And I cannot understand how it works. Can someone help me to understand this problem?
Thank you for trying)
The Java Language Specification (JLS), in the context of the compiler resolving a method invocation expression, states
If the form is
super . [TypeArguments] Identifier
, then the class to search is the superclass of the class whose declaration contains the method invocation.
The class whose declaration contains the method invocation, in this case, is the anonymous Solution
subclass and its superclass is Solution
. The JLS, in the context of determining which instance will be used to invoke the method, then goes on to say
If the form is
super . [TypeArguments] Identifier
, then the target reference is the value ofthis
.
this
, in this case, refers to the instance of the anonymous Solution
subclass. That instance's name
field was initialized with the value "sout"
as so that is what getName()
returns.
In the original sample,
new Solution("sout") {
void printName() {
System.out.println(getName());
}
}.printName();
the getName()
method invocation is unqualified and therefore a different rule applies. That is
If there is an enclosing type declaration of which that method is a member, let
T
be the innermost such type declaration. The class or interface to search isT
.
T
, here, is the Solution
class, since it is the innermost enclosing type of the anonymous Solution
subclass and getName()
is its member.
Then, the JLS states
Otherwise, let
T
be the enclosing type declaration of which the method is a member, and letn
be an integer such thatT
is the n'th lexically enclosing type declaration of the class whose declaration immediately contains the method invocation. The target reference is the n'th lexically enclosing instance ofthis
.
Again, T
is Solution
, the 1st lexically enclosing type since the class whose declaration immediately contains the method invocation is the anonymous Solution
subclass. this
is the anonymous Solution
subclass instance. The target reference is therefore the 1st lexically enclosing instance of this
, ie. the Solution
instance, whose name
field was initialized with the value "main"
. That's why the original code prints "main"
.
The behavior may seem counter-intuitive but it becomes clear with a bit of refactoring.
So, the sout()
method can actually be rewritten as
private void sout() {
new Solution("sout") {
void printName() {
String name = getName();
System.out.println(name);
}
}.printName();
}
public static void main(String[] args) {
Solution mainSolution = new Solution("main");
mainSolution.sout();
}
Calling the sout()
method of the mainSolution
object, creates a child Solution
object that has an additional printName()
method, which calls
getName();
which only declared in the parent mainSolution
object.
If getName()
is declared as private, it is not overriden but it is still accessible from the inner class, so getName()
refers to the name of the mainSolution
, i.e. to main
.
If getName()
has no modifier, or is declared as protected or public, then it is inherited (overriden) and refers to the child Solution
object's name, i.e. to sout
, hence "sout" will be printed.
By replacing getName()
in sout()
with
Solution.this.getName()
the string "main" will be printed in both scenarios.
By replacing it with either of
this.getName()
super.getName()
if the getName()
method is declared as private
a compilation error will occur, otherwise the string "sout" will be printed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With