为什么method()和super.method()引用匿名子类中的不同事物?

时间:2018-07-02 22:26:20

标签: java inheritance inner-classes

我正在解决一些练习,以更好地理解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();
}

我不明白它是如何工作的。有人可以帮助我了解这个问题吗?

感谢您的尝试)

2 个答案:

答案 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的词法包围实例。

同样,TSolution,这是第一个词法包围类型,因为其声明直接包含方法调用的类是匿名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”。