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

时间:2015-10-08 21:45:27

标签: java nested-class

为什么我不能这样做/是否有解决方法来实现这一目标:

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

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

    想要的四种解决方法/解决方案,以及我不想要它们的原因:

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

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

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

    4. 移除。请参见底部的编辑。

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

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

      感谢您的帮助。

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

2 个答案:

答案 0 :(得分:11)

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

因此,您要将C修复为:

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

然后您可以使用以下方法创建新的C

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

评论中问题的答案

  • @Andi Turner问:

      

    如果你明确地将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甚至如果它实际上没有使用它。 myAmyC之间的关联是myA是关于myC直接封闭的B实例。该术语取自section 8.1.3 of the JLS

      

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

此用法的官方参考

此用法称为限定的超类构造函数调用语句,在JLS, section 8.8.7.1 - Explicit Constructor Invocations中提及。

  

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

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

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

  •   

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

答案 1 :(得分:3)

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

  

更新:您已经提到过您不想要第一个解决方案,但问题的措辞可能会导致人们 我希望内心阶级是静止的,所以我希望这对他们有用。对于您的确切问题,更恰当的答案在本答复的第二部分。

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

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