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

Why can't outer classes extend inner classes?

提问人:snickers10m 提问时间:10/9/2015 最后编辑:snickers10m 更新时间:10/9/2015 访问量:2994

问:

为什么我不能这样做/是否有解决方法可以做到这一点:

package myPackage;

public class A {
    public class B {

    }
}

package myPackage;  

import myPackage.A.B;

public class C extends B {

}

package myPackage;

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

两个编译错误是

  1. public class C extends BNo 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 祖先的封闭实例。但是,如果 B 具有访问 A 的封闭实例的功能,则需要通过 JoshuaTaylor 方法的匿名类或通过 RealSkeptic 方法的任何其他类。

Java 嵌套类

评论

3赞 Joshua Taylor 10/9/2015
如果 B 不是静态的,则 B 的实例具有对包含 A 的引用。如果你让 B 静态 (),你能做你想做的事情吗?public static class B
0赞 snickers10m 10/9/2015
@JoshuaTaylor 谢谢你的提及。我不想那样。我会将其添加到我不想要的解决方案列表中,以及为什么我不想要它。

答:

3赞 Joshua Taylor 10/9/2015 #1

您可以轻松扩展嵌套的静态

更新:你提到你不想要第一个解决方案,但这个问题的措辞可能会引导人们愿意让内部类是静态的,所以我会留下这个,希望它对他们有用。您的确切问题的更正确答案在本答案的第二部分。

您可以,但内部类必须是静态的,因为如果不是,则内部类的每个实例都具有对外部类的封闭实例的引用。静态嵌套类没有该引用,您可以自由扩展它。

public class Outer {
    public static class Inner {

    }
}
public class InnerExtension extends Outer.Inner {

}

但您也可以扩展嵌套的非静态类

package test;

public class Outer {
    public class Inner {
        public String getFoo() {
            return "original foo";
        }
    }
}
package test;

public class Extender {
    public static void main(String[] args) {
        // An instance of outer to work with
        Outer outer = new Outer();

        // An instance of Outer.Inner 
        Outer.Inner inner = outer.new Inner();

        // An instance of an anonymous *subclass* of Outer.Inner
        Outer.Inner innerExt = outer.new Inner() {
            @Override
            public String getFoo() {
                return "subclass foo";
            }
        };

        System.out.println("inner's class: "+inner.getClass());
        System.out.println("inner's foo: "+inner.getFoo());
        System.out.println();
        System.out.println("innerExt's class: "+innerExt.getClass());
        System.out.println("innerExt's foo: "+innerExt.getFoo());
    }
}
inner's class: class test.Outer$Inner
inner's foo: original foo

innerExt's class: class test.Extender$1
innerExt's foo: subclass foo

评论

0赞 snickers10m 10/9/2015
我很抱歉让我的问题成为一个移动的目标。我只是编辑了它,说我不希望类是静态的。
0赞 snickers10m 10/9/2015
是的,这是解决问题的有效方法,但如果我需要一个封闭的实例,则不会。
0赞 snickers10m 10/9/2015
好“更新”您的问题。如果你想做进一步的编辑,你可以为我的另外两个“我不想要”添加代码示例,以帮助那些没有这些限制的人。
0赞 snickers10m 10/9/2015
给我一点时间。我正在测试你的代码。我发誓我之前尝试过这个,但没有用。
0赞 Joshua Taylor 10/9/2015
@snickers10m 实际上,我刚刚意识到您可以创建非静态嵌套类的子类的实例。您需要一个外部类的实例来执行此操作,但随后可以使用 进行子类化。new ... () { ... }
14赞 RealSkeptic 10/9/2015 #2

嗯,这是可以做到的,但你必须记住,每个构造函数都需要显式或隐式地调用它的超级构造函数。这就是为什么您会收到“由于某些中间构造函数调用,没有可用的 A 型封闭实例”错误。的 no-args 构造函数试图隐式调用 的 no-args 构造函数,如果没有 .CBA

所以你把你固定为:C

public class C extends B {
    public C(A enclosing) {
        enclosing.super();
    }
}

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

A myA = new A();
C myC = new C(myA);

评论中问题的答案

  • @Andi特纳问道:

    如果您将 A 显式传递给 C 的构造函数,那么 C 现在不能是静态的,并且将 A 作为 C 中的“普通旧”成员变量,并在其上调用所需的方法吗?

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

  • @rajuGT问道:

    C 是一个单独的实体吗?如果是这样,为什么需要 A 对象?在这种情况下,myA 和 myC 之间有什么关联?

    是的,C 是一个单独的实体。它不需要 A 来使用它自己的任何方法。但是,如果它试图从 B 调用(或继承并且不覆盖)涉及访问 A 的方法,那么 B 的实现就需要 A。 当然,从形式上讲,B 的任何实例都需要对 A 的引用,即使它实际上没有使用它。和 are 之间的关联是 myC 相对于 B 的直接封闭实例。该术语取自 JLS 的第 8.1.3 节myAmyCmyA

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

此用法的官方参考

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

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

  • 不合格的超类构造函数调用以 关键字(可能以显式类型参数开头)。super

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

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

评论

1赞 snickers10m 10/9/2015
哦,我的上帝。它有效。它只是工作。非常感谢。这正是我一直在寻找的。
2赞 Joshua Taylor 10/9/2015
嗯,这很整洁。我比我的“解决方案”更喜欢它。
2赞 snickers10m 10/9/2015
@AndyTurner Plus,它解释了问题的原因,这始终是解决方法的胜利。
1赞 RealSkeptic 10/9/2015
没关系,他说它有效,但对它只适用于内部班级这一事实感到惊讶...... @raju - 明天早上我会尝试找到最好的官方参考资料。
2赞 RealSkeptic 10/9/2015
@rajuGT 我已经编辑了答案,并添加了更多信息和对 JLS 的引用。我希望你觉得它有用。就我个人而言,我从旧书《Java 编程语言》中知道了这种用法,可惜,它在 Java 5 之后没有更新,但仍然是一个很好的来源。