为什么需要提供封闭类对象而不是嵌套类对象

9
class OuterA {
    class InnerA {
    }
}
class SubclassC extends OuterA.InnerA { 
    SubclassC(OuterA outerRef) {
        outerRef.super(); 
    }
}
class XYZ {
    public static void main(String[] args) {
        new SubclassC(new OuterA());
    }
}

在上述代码中:
  1. 为什么我需要将OuterA对象引用传递给SubclassC构造函数才能编译.java文件?
  2. 为什么不需要将InnerA对象引用传递给SubclassC构造函数?

1
请注意,如果您的 InnerA 类不需要绑定到 OuterA 的实例上,您可以将其声明为 static,这个 "问题" 就会消失。根据您的评论,听起来这可能会有所帮助。 - Giovanni Botta
4个回答

6
为什么我需要将OuterA对象引用传递给SubclassC构造函数才能编译.java文件?
因为SubclassC扩展了InnerA类的定义。同时,InnerA类与OuterA绑定(即所有InnerA实例都将绑定到相应的OuterA实例)。因此,要获取SubclassC的实例,您需要一个OuterA的实例。
为什么不需要传递InnerA对象引用给SubclassC构造函数?
因为创建SubclassC时,其超类的实例将首先被创建,即会有一个InnerA实例。实际上,您可以将SubclassC看作是一种特殊类型的InnerA。

但是为什么语言不允许你拥有一个无参的SubclassC构造函数,这样你就可以像编写new OuterA().new InnerA()一样编写new OuterA().new SubclassC()?我错过了什么吗? - Paul Boddington
这是由于 InnerA 的定义所必需的。InnerA(以及其所有子类)将始终需要一个 OuterA 来实例化。否则,(如果您不必提供 OuterA 实例)您将打破 OuterAInnerA 之间的联系(即,您将忽略 InnerA 嵌套在 OuterA 中的事实)。如果 InnerAstatic,则不需要 - Konstantin Yovkov
但是我确实提供了一个 OuterA 实例。 - Paul Boddington
@pbabcdefp,new OuterA().new SubclassC()不被允许,因为SubclassC并没有嵌套在OuterA中。 - Konstantin Yovkov
@kocko 我知道从多态的角度来看,SubclassC 是 InnerA 的一个实例。因此我们有两个不同的 InnerA 实例和 SubclassC 实例。但是我们只创建了一个属于 SubclassC 的对象。除了 InnerA 实例所引用的 SubclassC 对象之外,我们在哪里创建了 InnerA 的独立对象? - paidedly
我们没有。当您创建SubclassC时,即使在SubclassC的构造函数之前调用了InnerA的构造函数,您也没有InnerA的实例。发生的情况是您有一个实例(它是SubclassC的实例),并且此实例与InnerA兼容(即可以转换)。需要调用InnerA的构造函数,因为它是SubclassC定义的一部分。 - Konstantin Yovkov

4
因为InnerAOuterA的内部类。这意味着OuterA.InnerA类型的对象或其任何子类型(例如SubclassC)只能存在于封闭类实例(在本例中为OuterA)的上下文中。
这被称为限定超类构造函数调用。根据JLS

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

如果您不需要将SubclassC实例链接到已经存在的OuterA实例,您可以在SuperclassC构造函数中创建新的OuterA对象。
static class SubclassC extends OuterA.InnerA {
    SubclassC() {
        new OuterA().super();
    }
}
  1. 为什么不需要将InnerA对象引用传递给SubclassC构造函数?

因为SubclassC扩展了OuterA.InnerA,所以当你执行outerRef.super()时,你正在调用OuterA.InnerA构造函数。要看到这一点,请考虑下面的代码:

public class Example {
    static class OuterA {
        OuterA() {
            System.out.println("Call OuterA constructor");
        }
        class InnerA {
            InnerA() {
                System.out.println("Call InnerA constructor");
            }

        }
    }

    static class SubclassC extends OuterA.InnerA {
        SubclassC(OuterA outerRef) {
            outerRef.super();
            System.out.println("Call SuperclassC constructor");
        }
    }

    public static void main(String[] args) {
        OuterA outerA = new OuterA();
        System.out.println("Before new SuperclassC()");
        new SubclassC(outerA);
    }
}

输出:

Call OuterA constructor
Before new SuperclassC()
Call InnerA constructor
Call SuperclassC constructor

很棒,你指出那实际上是在调用InnerA ctor! - Raffaele

2
1.Why do I need to pass OuterA object reference to SubclassC constructor for the .java file to compile? 

因为您需要知道类的实例名称才能调用函数,因为SubclassC扩展了类OuterA.InnerASubclassC extends OuterA.InnerA {

SubclassC(OuterA outerRef) {
        outerRef.super(); 

第二个

2. Why is InnerA object reference not required to be passed to SubclassC constructor?

因为Innera在Outtera内部,当您创建对象new SubclassC(new OuterA());时调用了Outtera(即在这种情况下不需要创建单独的对象,因为InnerAOuterA内部)。在这种情况下,创建OuterA就足够了。


因为你需要知道对象的名称 - 对象没有名称。 - davmac
将其更改为类实例的名称。 - adrCoder
1
实例是对象,它们都没有名称。你不需要“知道对象的名称”,而是需要有一个对该对象的引用。 - davmac

2

在运行构造函数之前,我们希望发生两件事:

  • 基类(如果未使用extends则为Object)的构造函数应该已经运行
  • 封闭实例(如果有的话)应该已经完全构建,并且它的引用正在程序中传递

Java通过自动在构造函数开头放置super()调用来执行第一条规则。请注意,不存在父实例:当您执行new File()时,不需要提供Object - 因此在构建SubclassC时不需要InnerA

当在另一个类的词法范围内定义非静态类时,它可以访问封闭对象的字段:

class Outer {
  int val;
  class Inner {
    int val() {
      return val; // <-- we can access val, it's in scope
    }
  }
}

所以很明显我们需要一个链接。这是通常创建链接的方式:

Outer outer = new Outer();
Outer.Inner inner = outer.new Inner(); // baroque, I admit

在扩展 Inner 类的第一行,发生了完全相同的事情。
class InnerExtended extends Outer.Inner {
  public InnerExtended(Outer outer) {
    outer.super(); // <-- calls Outer.Inner ctor
  }
}

即使相似之处不易察觉:
super();            // plain superclass ctor call
outer.new Inner();  // instantiation of inner class
outer.super();      // super() call in class extending inner

1
我刚刚找到了看似不一致的原因。如果InnerExtended也嵌套在Outer中,那么每个InnerExtended实例实际上都需要两个Outer实例(Inner的封闭实例和InnerExtended的封闭实例)。这些可以是不同的!编写outer1.new InnerExtended(outer2)将使outer1成为InnerExtended的封闭实例,而outer2则是其父级的封闭实例。当InnerExtended没有嵌套在Outer中时,只需要一个Outer,但语法必须一致。 - Paul Boddington

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