为什么外部类不能扩展内部类?

sni*_*10m 7 java nested-class

为什么我不能这样做/是否有解决方法来完成此任务:

package myPackage;

public class A {
    public class B {

    }
}
Run Code Online (Sandbox Code Playgroud)
package myPackage;  

import myPackage.A.B;

public class C extends B {

}
Run Code Online (Sandbox Code Playgroud)
package myPackage;

public class Main {
    public static void main(String[] args) {
        A myA = new A();
        C myC = myA.new C();
    }
}
Run Code Online (Sandbox Code Playgroud)

两个编译错误是

  1. 在public class C extends B,No enclosing instance of type A is available due to some intermediate constructor invocation

  2. 在C myC = myA.new C();,A.C cannot be resolved to a type

坦率地说,我认为概念是合理的:我想制作B的子类,这样当我为A制作B时,我可以选择使其具有B中的功能或C中的功能.

我不想要的四种解决方法/解决方案,以及为什么我不想要它们:

  1. "解决方案:将C放入A中." 我不想这样,因为如果我不能修改A.java的代码(有应用程序有这个限制)怎么办?如果A是另一个API的一部分怎么办?然后我必须为C创建一个新文件,就像我在这里所做的那样.

  2. "解决方案:将C放在扩展A的D类中." 我不希望这样,因为C只限于在类型D的实例上实例化.我想创建一个扩展B的类,可以在类型A的所有实例上实例化(有些应用程序需要这个).因此,我需要C不被其他类封闭,就像我在这里所做的那样.

  3. (作为问题编辑添加 - 请参阅JoshuaTaylor对代码示例的回答)"解决方案:使B静态." 我不希望这样,因为如果B中的功能需要访问其封闭的A实例(有应用程序需要这个),该怎么办?因此,我需要B不是静态的,就像我在这里所做的那样.(第二个问题编辑:你可以使B静态并让它的构造函数接受它的封闭实例,将它保存在受保护的变量中以便在其子代中访问,但这不如RealSkeptic接受的答案那么优雅)

  4. 删除.请参见底部的编辑.

所以,如果你的回答表明我做了上述其中一个,那么这不是这个问题的答案,即使它可能对其他人有用.

如果您的回答是"这只是Java语言的一个缺陷,那么您根本无法实现这个概念性的想法",这是一个好的答案,您应该发布它.但是只是一个警告:如果您错了,我会推迟将您的答案标记为已接受.如果这是您的答案,我将非常感谢您解释为什么对该语言的这种限制(因为这是该问题的标题).

感谢您的帮助.

编辑:JoshuaTaylor的回答提出了一个有效的选择:你可以匿名扩展B.并避免像在RealSkeptic接受的答案中那样编写构造函数.我最初放弃了这个想法,因为它不允许你通过"A.this"访问C的封闭A实例.但是,我已经知道C没有A的封闭实例,除非它在A的定义中作为嵌套类特别定义.所以请注意:以下解决方案都不允许您通过在C方法中写"A.this"来访问包含C的祖先B的A的封闭实例.类只能使用".this"来访问它们的类型具体嵌套在.但是,如果B具有访问A的封闭实例的功能,则需要通过JoshuaTaylor方法的匿名类或通过RealSkeptic方法的任何其他类.

Rea*_*tic 11

好吧,它可以完成,但你必须记住,每个构造函数都需要显式或隐式地调用它的超级构造函数.这就是为什么你得到"由于某些中间构造函数调用而没有A类的封闭实例"错误.C'no-args构造函数试图隐式调用B'no-args构造函数,如果没有一个,它就不能这样做A.

所以你修复你的C:

public class C extends B {
    public C(A enclosing) {
        enclosing.super();
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以C使用以下方法创建一个新的:

A myA = new A();
C myC = new C(myA);
Run Code Online (Sandbox Code Playgroud)

答案中的问题

  • @Andi特纳问道:

    如果你明确地将A传递给C的构造函数,那么C现在不能是静态的,并且在C中使用A作为"普通旧"成员变量来调用所需的方法吗?

    应该注意的是,C既不是静态的也不是内部的.它是一个单独的公共类,它扩展了一个内部类B.C的作者可能不知道B类的实现,所以它不知道使用A的方法是什么,也不能访问任何私有成员A,因为C不是A的成员但是B确实如此,并且B需要A实例.另一种方法是组合而不是继承(其中C持有B实例并将操作委托给它),但是如果它想要创建该B实例而不是将其传递到内部,它仍然需要一个A实例,尽管它会使用enclosing.new B而不是enclosing.super.

  • @rajuGT问:

    C是个体吗?如果是这样,为什么它需要A对象?在这种情况下,myA和myC之间的联系是什么?

    是的,C是一个单独的实体.对于任何自己的方法都不需要A. 但是如果它试图调用(或继承并且不覆盖)B中涉及访问A的方法 - 则B的实现需要A.当然,任何B的实例都需要引用A even如果它实际上没有使用它.之间的关联myA和myC是是myA是立即包围实例myC相对于B.该术语取自JLS第8.1.3节:

    对于每一个超类S的C其本身是一个直接的内部类的类或接口的SO,有的一个实例SO与相关联的i,被称为的立即封闭实例i相对于S.当通过显式构造函数调用语句调用超类构造函数时,确定对象的直接封闭实例(如果有的话)(§8.8.7.1)

官方参考此用法

这种用法称为限定的超类构造函数调用语句,在JLS的8.8.7.1节 - 显式构造函数调用中提到.

超类构造函数调用以关键字super (可能以显式类型参数开头)或主 表达式或ExpressionName开头.它们用于调用直接超类的构造函数.他们进一步分裂:

  • 非限定的超类构造函数调用以关键字super开头(可能以显式类型参数开头).

  • 合格的超类构造函数调用以Primary 表达式或ExpressionName开头.它们允许子类构造函数显式指定新创建的对象关于直接超类(第8.1.3节)的直接封闭实例.当超类是内部类时,这可能是必要的.

在该部分的末尾,您可以找到显式构造函数调用语句的示例,包括此用法.

  • 嗯这很漂亮.我比它的"解决方案"更喜欢它. (2认同)
  • @AndyTurner Plus,它解释了问题的原因,这总是赢得一个解决方法. (2认同)
  • @rajuGT我编辑了答案并添加了更多关于JLS的信息和参考资料.希望对你有帮助.就个人而言,我从旧书[Java编程语言](http://www.amazon.com/The-Java-Programming-Language-Edition/dp/0321349806)中了解到这种用法,唉,之后还没有更新Java 5但仍然是一个很好的来源. (2认同)