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

2022-09-03 14:45:15

我正在解决一些练习,以更好地了解java中的内部类是如何工作的。我发现了一个非常有趣的练习。练习的条件是使打印“sout”而不是“主要”,并且更改最少。有它的代码:printName()

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和有-A连接。这意味着匿名内部类扩展了外部类,并且内部类的对象也具有对外部类的对象的引用。如果运行上面的代码,将打印“main”。子级不能通过继承调用父项。但是子类是内部类使用对父类(外部类)的引用来访问该方法。getName()

解决此任务的最简单方法是将 的访问修饰符更改为其他任何内容。所以孩子能够通过继承使用,并且由于后期装订“sout”将被打印出来。getName()privategetName()

解决此任务的另一种方法是使用 。super.getName()

private void sout() {
    new Solution("sout") {
        void printName() {
            System.out.println(super.getName());
        }
    }.printName();
}

我无法理解它是如何工作的。有人可以帮助我理解这个问题吗?

感谢您的尝试)


答案 1

Java 语言规范 (JLS) 在解析方法调用表达式的编译器上下文中指出

如果形式是 ,则要搜索的类是其声明包含方法调用的类的超类。super . [TypeArguments] Identifier

其声明包含方法调用的类(在本例中)是匿名子类,其超类是 。在确定将使用哪个实例来调用该方法的上下文中,JLS继续说SolutionSolution

如果形式为 ,则目标引用是 的值。super . [TypeArguments] Identifierthis

this,在本例中,引用匿名子类的实例。该实例的字段已使用值初始化,因此返回值即返回。Solutionname"sout"getName()


在原始示例中,

new Solution("sout") {
    void printName() {
        System.out.println(getName());
    }
}.printName();

方法调用是非限定的,因此适用不同的规则。那是getName()

如果存在一个封闭类型声明,该方法是该方法的成员,则让它成为最里面的此类类型声明。要搜索的类或接口是 。TT

T,这里,是类,因为它是匿名子类的最内层封闭类型,并且是其成员。SolutionSolutiongetName()

然后,JLS声明

否则,设为该方法所属的封闭类型声明,并设为一个整数,该整数是该类的第 n 个词法封闭类型声明,其声明立即包含方法调用。目标引用是 的第 n 个词法封闭实例。TnTthis

同样,是第一个词法封闭类型,因为其声明立即包含方法调用的类是匿名子类。 是匿名子类实例。因此,目标引用是 的第一个词法封闭实例,即。该实例,其字段已使用值 初始化。这就是原始代码打印的原因。TSolutionSolutionthisSolutionthisSolutionname"main""main"


答案 2

这种行为可能看起来有悖常理,但通过一些重构就会变得清晰起来。

因此,该方法实际上可以重写为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()mainSolutionSolutionprintName()

getName();

仅在父对象中声明。mainSolution

如果声明为私有,则不会覆盖它,但它仍然可以从内部类访问,因此引用的名称,即。getName()getName()mainSolutionmain

如果没有修饰符,或者被声明为受保护或公共,那么它将被继承(覆盖)并引用子对象的名称,即 to ,因此将打印“sout”。getName()Solutionsout

通过将 in 替换为getName()sout()

Solution.this.getName()

字符串“main”将在两种情况下打印。

通过将其替换为

this.getName()
super.getName()

如果将方法声明为编译错误,则会发生错误,否则将打印字符串“sout”。getName()private


推荐