我正在解决一些练习,以更好地理解Java内部类的工作方式。我发现了一个非常有趣的练习。练习的条件是使printName()
打印“ sout”而不是“ main”并进行最小的更改。有它的代码:
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();
}
}
我们遇到了一个有趣的情况-这两个类具有is-A和has-A连接。
这意味着匿名内部类扩展了外部类,并且内部类的对象也引用了外部类的对象。
如果您运行上面的代码,则将打印“ main”。子级无法通过继承调用父级的getName()
。但是作为内部类的子级使用对父类(外部类)的引用来访问该方法。
解决此任务的最简单方法是将getName()
的访问修饰符从private
更改为其他任何内容。这样孩子就可以通过继承使用getName()
了,并且由于后期绑定,“ sout”将被打印出来。
解决此任务的另一种方法是使用super.getName()
。
private void sout() {
new Solution("sout") {
void printName() {
System.out.println(super.getName());
}
}.printName();
}
我不明白它是如何工作的。有人可以帮助我了解这个问题吗?
感谢您的尝试)
答案 0 :(得分:10)
Java语言规范(JLS)in the context of the compiler resolving a method invocation expression声明
如果表单为
super . [TypeArguments] Identifier
,则该类为 search是其声明包含以下内容的类的超类: 方法调用。
其声明包含方法调用的类 ,在这种情况下为匿名Solution
子类,其超类为Solution
。 JLS in the context of determining which instance will be used to invoke the method,然后继续说
在这种情况下,如果表单为
super . [TypeArguments] Identifier
,则目标 引用是this
的值。
this
指的是匿名Solution
子类的实例。该实例的name
字段已使用值"sout"
进行了初始化,因此getName()
返回了该值。
在原始示例中,
new Solution("sout") {
void printName() {
System.out.println(getName());
}
}.printName();
getName()
方法调用是不合格的,因此适用其他规则。那是
如果有一个封闭的类型声明,该方法是 成员,让
T
是最里面的类型声明。该类或 搜索界面为T
。
T
,这里是Solution
类,因为它是匿名Solution
子类的最内层封闭类型,而getName()
是其成员。
然后,JLS声明
否则,让
T
为该方法的封闭类型声明 是成员,令n
为整数,使得T
为第n个词 紧随其声明的类的类型声明 包含方法调用。目标参考是第n个this
的词法包围实例。
同样,T
是Solution
,这是第一个词法包围类型,因为其声明直接包含方法调用的类是匿名Solution
子类。 this
是匿名Solution
子类实例。因此,目标引用是this
的第一个词汇包围实例,即。 Solution
实例,其name
字段已使用值"main"
初始化。这就是原始代码显示"main"
的原因。
答案 1 :(得分:5)
该行为看似违反直觉,但经过一些重构后变得很明显。
因此,sout()
方法实际上可以重写为
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();
}
调用sout()
对象的mainSolution
方法,将创建一个子Solution
对象,该对象有一个附加的printName()
方法,该对象会调用
getName();
仅在父mainSolution
对象中声明。
如果getName()
被声明为私有,则它不会被覆盖,但仍可以从内部类访问,因此getName()
指的是mainSolution
的名称,即{{ 1}}。
如果main
没有修饰符,或者被声明为受保护的或公共的,则它被继承(覆盖)并引用子getName()
对象的名称,即Solution
,因此“ sout”将被打印。
通过将sout
中的getName()
替换为
sout()
在两种情况下都将打印字符串“ main”。
通过将其替换为任何一个
Solution.this.getName()
如果将this.getName()
super.getName()
方法声明为getName()
,则会发生编译错误,否则将打印字符串“ sout”。