Java:非静态嵌套类和instance.super()

17

我很难理解Java中的非静态嵌套类。考虑以下示例,它将打印“Inner”和“Child”。

class Outer {
    class Inner {
        Inner() { System.out.println("Inner"); }
    }
}

public class Child extends Outer.Inner {
    Child(Outer o) {
        o.super();
        System.out.println("Child");
    }
    public static void main(String args[]) {
        new Child(new Outer());
    }
}

我知道 Inner 的实例必须始终与 Outer 实例相关联,并且由于 Child 扩展了 Inner,因此也适用于 Child。 我的问题是 o.super() 语法的含义 - 为什么它调用 Inner 构造函数?

我只看过使用 super(args) 来调用超类构造函数和 super.method() 调用覆盖方法的超类版本,但从未见过形式为 instance.super() 的内容。


哈哈...这个有什么和“面试问题”标签有关系吗? - Cristian
在Java职位的面试中,我被要求完成了一份IKM测验;这个问题是测验中一个简化版本的形式。 - Kiv
@Kiv 你有现实生活中使用的示例吗? - chepseskaf
5个回答

11

内部类(非静态子类)本质上是带有对其父对象的隐式链接的嵌套类(静态子类)。下面是使用静态嵌套类编写的您上面的代码:

class Outer {
    static class Inner {
        final Outer outer;
        Inner(Outer outer) {
            this.outer = outer;
            System.out.println("Inner");
        }
    }
}

public class Child extends Outer.Inner {
    Child(Outer o) {
        super(o); // o.super();
        System.out.println("Child");
    }

    public static void main(String args[]) {
        new Child(new Outer());
    }
}

通过这个,你应该能够理解o.super()在做什么。


2
事实上,非静态内部类本质上是上述内容的语法糖,据我理解。 - Luke Maurer
不,嵌套的静态类本质上是一个外层级别的类,仅仅是为了方便打包而存在。 - Adeel Ansari
2
好的,我只是指一个非静态内部类是语法糖的语法糖:-D - Luke Maurer

11
这被称为“合格的超类构造函数调用”。
引自这里
显式构造函数调用语句可以分为两种类型:
  • 备用构造函数调用以关键字this开头(可能带前缀的明确类型参数)。它们用于调用同一类别的备用构造函数。

  • 超类构造函数调用以关键字super(可能带前缀的明确类型参数)或主表达式开头。它们用于调用直接超类的构造函数。超类构造函数调用可以进一步细分:

  • 未限定的超类构造函数调用以关键字super(可能带前缀的明确类型参数)开头。

  • 限定的超类构造函数调用以主表达式开头。它们允许子类构造函数明确指定新创建对象相对于直接超类的立即封闭实例(§8.1.3)。当超类是内部类时,这可能是必要的。


6
为什么在Child中执行o.super()会调用Outer.Inner的构造函数呢?原因很简单:因为Child继承了Outer.Inner,而构造函数总是沿着层次结构链进行链接。
以下是稍微扩展了您的代码片段以说明问题:
class Outer {
    Outer() {
        System.out.println("Outer");
    }
    void outerMethod() { }
    class Inner {
        Inner() {
            System.out.println("OuterInner");
            outerMethod();              
        }
        String wealth;
    }
}
class OuterChild extends Outer {
    OuterChild() {
        System.out.println("OuterChild");
    }
}
public class OuterInnerChild extends Outer.Inner {
    OuterInnerChild(Outer o) {
        o.super();
        System.out.println("OuterInnerChild");
        this.wealth = "ONE MILLION DOLLAR!!!";
    }
    public static void main(String args[]) {
        System.out.println(new OuterInnerChild(new Outer()).wealth);
        new OuterChild();
    }
}

这将打印:

Outer
OuterInner
OuterInnerChild
ONE MILLION DOLLAR!!!
Outer
OuterChild

一些关键的观察:
- 因为 `OuterInnerChild extends Outer.Inner`,所以它像普通子类语义一样继承了 `wealth` - 就像普通子类语义一样,`OuterInnerChild` 的构造函数链接到 `Outer.Inner` 的构造函数 - 因为 `OuterChild extends Outer`,所以即使没有显式调用,它的构造函数也会链式调用
编译器为什么要求 `OuterInnerChild` 构造函数接受一个 `Outer o` 参数,并调用 `o.super()`?
这是内部类语义上的特殊要求:目的是确保所有的 `OuterInnerChild` 实例都有一个封闭的 `Outer` 实例,供 `OuterInnerChild` 的超类 `Outer.Inner` 使用。否则,`Outer.Inner` 的构造函数将没有一个封闭的 `Outer` 实例来调用 `outerMethod()`。

2
概念上,非静态内部类“属于”特定的对象。这有点像每个对象都有自己版本的类,就像非静态字段或方法属于特定的对象一样。
因此,我们才会有奇怪的语法,例如instance.new Inner()instance.super()——用于那些“但是谁的Inner?”问题不是立即显而易见的情况。(在外部类的非静态方法中,您可以直接使用new Inner(),通常这相当于this.new Inner()。)

0

在编写程序时,我们要始终牢记基本原则:在调用子类构造函数的过程中,无论是内部类还是外部类,父类都会首先被实例化。在您的情况下,由于您正在扩展内部类,并且您的内部类是父类的成员,因此需要先实例化父类,然后再调用实际的内部类构造函数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接