为什么内部类的子类需要一个封闭实例?

8

考虑类 OuterClass,其中包含 InnerClass

public class OuterClass {
    class InnerClass {

    }
}

第二种情况是试图扩展 OuterClassInnerClass

public class Clazz extends OuterClass.InnerClass {
    public Clazz(OuterClass outerClass) {
        outerClass.super();
    }
}

到目前为止,这段代码将会运行,并且编译器不应该出现警告。
但是我正在尝试理解——为什么需要传递OuterClass的引用给构造函数?为什么需要调用它的超类构造函数?
我想要理解为什么必须是这个确切的方式?
此外,请考虑以下类(与之前的类无关),其中类ApartmentsHallBuilding类的内部类,而Building类又是Solution的内部类(内部嵌套)。
public class Solution {
    public class Building {
        public class Hall {
            private BigDecimal square;

            public Hall(BigDecimal square) {
                this.square = square;
            }
        }

        public class Apartments {
        }
    }
}

然后有一个顶层类,试图扩展内部内部类Hall。
class BigHall extends Solution.Building.Hall {
    public BigHall(Solution.Building building, BigDecimal square)
    {
        building.super(square);
    }
}

看看这个混乱的代码。最后两个类也应该编译,但是你能为我澄清一下吗?为什么在扩展内部内部类Hall时,BigHall类需要将对Building对象的引用传递给构造函数,而不是Solution对象?

如果您可以提供JLS的引用,那就太好了!


3
因为InnerClass不是static,所以它隐式地引用了其父类OuterClass的实例。然而,我完全不知道为什么会这样做 - 如果你嵌套了3层类,那么肯定出了非常大的问题。 - Boris the Spider
3
一个顶级类不应扩展一个嵌套类,特别是内部类。内部类的实例只能存在于其包含类的实例中。 - Lew Bloch
1
我认为如果你觉得有必要这样做,内部类就不应该是一个内部类。我认为你最好利用packages - Fildor
1
JLS §8.1.3 - Boris the Spider
你应该真正使用组合而不是你试图完成的东西。 - Murat Karagöz
我应该澄清一下:这不是一个真实的用例,问题仅出于学术兴趣。 - a.anonymous
4个回答

7

你的InnerClass是一个内部类

内部类是一个嵌套类,它没有被明确或隐式地声明为static

内部类可以有封闭实例。

一个直接内部类C [你的InnerClass] 的实例i 属于类或接口O [你的OuterClass] 的一个实例,称为i直接封闭实例

只有在静态上下文中声明的内部类没有封闭实例。

在静态上下文中声明的内部类I的实例没有词法封闭实例。

你的示例内部类不在静态上下文中,因此需要一个封闭实例。

Java语言规范 接着说明:

非私有内部成员类的构造函数会隐式声明一个形式参数,代表该类的直接封闭实例。

(如果您感兴趣,可以深入了解为什么如此)。换句话说,您的 InnerClass 构造函数实际上看起来像这样:

public InnerClass(OuterClass OuterClass.this){} // this is valid syntax for entirely different reasons

它期望一个类型为OuterClass的参数作为其封闭实例。子类化此InnerClass类型不会改变这一点,特别是因为任何子类型都必须调用其超类型的超级构造函数。
关于构造函数,规范也说明了

如果一个构造函数体没有以显式构造函数调用开始,并且被声明的构造函数不是原始类Object的一部分,则构造函数体隐含地开始于超类构造函数调用"super();",即直接超类的构造函数,不带参数。

所以很明显,没有这个参数,你的代码将无法工作。
public class Clazz extends OuterClass.InnerClass {
    public Clazz() {
        // implicit super() invocation
    }
}

这个super() 构造函数调用不能工作。在这种情况下,是因为它的语法是错误的。但是,主要意思是父构造函数期望传入一个参数来表示封装实例,但是你没有提供一个。正确的语法是你已经有的那个。让我们定义它。
多种构造函数调用。您的
public Clazz(OuterClass outerClass) {
    outerClass.super();
}

这是一个合格的超类构造函数调用定义为

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

章节进一步解释了如何评估此表达式。

如果超类构造函数调用是有限定的,那么在“.super”之前的Primary表达式或者ExpressionName表达式p将被评估。否则,该评估结果就是与S相关的立即封闭的实例i
换句话说,由outerClass引用的对象成为您的Clazz实例所需的封闭实例
简单来说,忽略内部类的情况,你的示例可以归结为以下内容。
public class Foo {}
public class Bar {
    public Bar(Foo foo){}
}
public class SubBar extends Bar {
    public SubBar(Foo foo) {
        super(foo);
    }
}
必须满足 的超级构造函数,该函数需要一个 实例。这与您的 类型发生的情况相同,只不过其超类型的构造函数是隐式的。

3

为什么需要将OuterClass的引用传递给构造函数?

因为InnerClass的实例只能存在于OuterClass的实例中,并且可以直接访问其封闭实例的方法和字段。这就是Java中内部(非静态)类的设计方式

更新 正如您要求的JLS参考,亲爱的@Boris the Spider在评论中提供了两次,它是JLS 8.1.3。但我个人认为JLS对此事的描述相当混乱。官方的Java Doc教程以更简单的方式解释了这些内容。


1
OP要求JLS参考资料-根据我的评论,这是JLS §8.1.3 - Boris the Spider
@BoristheSpider 谢谢,我已经添加了链接并在我的回答中提到了你。 - Andremoniy
我没有对这个答案进行负评,实际上我不同意这个 DV,因为这个答案回答了问题。但是:我有一种感觉,这有点 X-Y。我有一种感觉,OP 在误用内部类的概念。 - Fildor
1
@Fildor 好的,他问了一个“为什么”的问题,我想我已经给出了答案。感谢您的支持。 - Andremoniy
这就是我说的:你回答了问题。尽管我认为向OP暗示他的误解是“好的”,但这并不是回答至少不会被踩的必要条件...只是我的个人意见。 - Fildor

0

请考虑以下内容

public class Outer {
    String outerString;

    class Inner { 
        public void doStuff() {
            System.out.println("Outer string is " + outerString);
        }
    }


    static class StaticInner { 
        public void doStuff() {
            // can't access outerString here
        }
    }
}

在这个例子中...每个Inner都需要一个Outer才能存在。Inner可以访问Outer的所有字段。

但是StaticInner不需要Outer就可以存在。它无法访问Outer的字段。


我有一种感觉,OP的困惑来自于误解内部类的作用是通过包的方式“分组”类。我认为他应该使用包而不是内部类。 - Fildor

0
一个内部类可以被其外部类之外的另一个类扩展。如果您正在扩展静态内部类(静态嵌套类),那么它是一个直截了当的实现。如果您正在扩展非静态内部类,则子类构造函数必须明确调用外部类的超类构造函数,使用外部类的实例。因为,在没有外部类实例的情况下,无法访问非静态内部类。
示例代码
    class OuterClass
{
    class InnerClassTwo
    {
        //Class as a non-static member
    }
}

//Extending non-static inner class or member inner class
class AnotherClassTwo extends OuterClass.InnerClassTwo
{
    public AnotherClassTwo()
    {
        new OuterClass().super();  //accessing super class constructor through OuterClass instance
    }
}

编译器生成的内部代码

class Outer$Inner  
{  
    final Outer this$0;  
    Outer$Inner()  
    {   super();  
        this$0 = Outer.this;  
    }
}

1
没有static内部类的概念。 - Sotirios Delimanolis

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