为啥扩展内部类的本地类不能访问内部类封闭实例?

Posted

技术标签:

【中文标题】为啥扩展内部类的本地类不能访问内部类封闭实例?【英文标题】:Why can't a local class that extends an inner class access the inner class enclosing instance?为什么扩展内部类的本地类不能访问内部类封闭实例? 【发布时间】:2015-10-09 00:42:47 【问题描述】:

(我一直在重新阅读该问题的标题并思考它看起来有多荒谬,但我向您保证这是对问题的最佳描述,并且我有一个实际应用程序,这是最好的结构。我发誓我我没疯。)

考虑以下问题。每个块都是一个单独的文件:

package myPackage;

public class A 

    public int i;

    public A(int i) 
        this.i = i;
    

    public class B 

    


package myPackage;

import myPackage.A.B;

public class Main 

    public static void main(String[] args) 
        class C extends B 
            public C(A enclosingInstance) 
                enclosingInstance.super();
            

            public void show() 
                System.out.println(A.this.i);
            
        
        A myA = new A(2);
        C myC = new C(myA);
        myC.show();
    


请注意,enclosingInstance 业务是解决涉及中间构造函数调用的问题。见"Why can't outer classes extend inner classes?"。

我希望输出为“2”。但是,我在System.out.println(A.this.i); 上有一个编译错误:

A 类型的封闭实例在范围内是不可访问的

我认为我试图解决的程序化概念是合理的:在 main 内创建一种新类型的 B 以提供给 A,它使用 B 类型可以访问的 A 中的东西。

那么我做错了什么,或者为什么这在 java 中是不可能的?

编辑/更新:请注意,将main 中的代码移动到非静态方法时会出现相同的错误。也就是说,我尝试将static void main 中的所有内容移动到class Main 的一个新的非静态方法go()。然后我将static void main 更改为单行new Main().go();。错误在同一个地方。因此,在静态上下文中定义 class C 似乎不是问题。

【问题讨论】:

@snickers10m B 是否使用 A 的非静态属性?还是私人的(静态的或非静态的)? @Dici B 不使用 A 中的任何内容 - B 中根本没有代码。但是作为 B 类型的 C 使用 A 的非静态公共实例变量 @ 987654336@. 不确定我是否同意重复,但无论如何。一个简单的解决方法是在B 上定义一个getOuterInstance(),它只返回A.this @snickers10m 好的,所以我认为这是糟糕的设计。这就像您试图通过将内部功能暴露给外部类来打破封装 @Dici 这行得通,但它有点像 hack。我希望有一个不添加复杂结构的实际解决方案。我也不同意重复的问题,但我正在慢慢地研究重复的问题,看看我能学到什么。 【参考方案1】:

您希望 A.this 引用 B 实例的封闭实例。但为什么要这样做?这不是语法的意思。 A.this 表示C 实例的封闭A 实例,这没有意义,因为C 不是A 的内部类。

为了更清楚地说明这一点,这里有一个示例,其中C A 的内部类。

public class A 

    public int i;

    public A(int i) 
        this.i = i;
    

    public class B 
        void foo() 
            System.out.println(A.this.i);
        
    

    public class C extends B 
        C(A a) 
            a.super();
        
        void bar() 
            System.out.println(A.this.i);
        
    

    public static void main(String[] args) 
        A a1 = new A(1);
        A a2 = new A(2);
        C c = a1.new C(a2);
        c.foo();
        c.bar();
    

这里C 扩展BCB 都是A 的内部类。因此,任何C 都有一个封闭的A 实例,并且当考虑为B 时,它也有一个封闭的A 实例,并且这些封闭的实例是不同的(事实证明foobar 打印不同的数字)。

所以,A.this 不可能是你想要的意思,因为它已经意味着别的东西。我猜语言设计者之所以没有提出其他语法来表示超类的封闭实例,是因为这样的语法会非常复杂,几乎没有回报(简单的解决方法已经存在)。

【讨论】:

所以基本上,在我的原始代码中,.this 可以指代两个不同的地方:Main 因为它是 Main 的本地类,A 因为它是内部的子类Main 的类,Java 开发人员只是说“每个类定义只有一个封闭实例”? 只是C不是A的内部类,所以没有封闭实例的概念。这就是我从答案中得到的,这是真的 @snickers10m 每个类定义不仅有一个封闭实例,因为内部类可以有内部类。因此,您可以在 A 中的 B 中包含 C 中的 D,并且任何 D 都将具有 3 个封闭实例(一个 A、一个 B 和一个 C)。再加上这些类中的任何一个都可以有带有许多封闭实例的超类,整个事情变得荒谬。语言允许你引用所有这些东西是不明智的。 @PaulBoddington 好的。我只是在检查我的理解。谢谢你的回答! @PaulBoddington 那么,为了检查一下,确定类的封闭实例的经验法则是什么?它总是定义类的文件的***类吗?或者它只是下一个级别的课程? (所以在 D 内 C 内 B 内 A 的情况下,D 的封闭实例是 C 还是 A?)【参考方案2】:

这是你永远不应该为生产编写的荒谬代码。

Explicit Constructor Invocations 的文档中有部分解释

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

所有这一切都表明C 是一个本地类 (which is an inner class, which is kind of nonsense because if you declare it in a static method there is no enclosing instance),它是B 的子类,但不是A 的嵌套类。因此,没有封闭的实例。 C 的实例没有封闭实例。 (虽然如果您在实例方法中声明它会这样,但那将是Main 的实例。)

新创建的对象的直接封闭实例(来自 JLS)是通过构造函数参数间接指定的。

你必须自己存储它

private A enclosingInstance;
public C(A enclosingInstance) throws CloneNotSupportedException 
    enclosingInstance.super();
    this.enclosingInstance = enclosingInstance;

由于A#ipublic,所以可以正常访问

public void show() 
    System.out.println(enclosingInstance.i);

【讨论】:

如果您查看我的编辑,我说我尝试将我的代码移动到 Main 中名为 go() 的实例方法中,并将 static void main 更改为 new Main().go()。错误在完全相同的位置。所以它与在静态上下文中定义无关。 @snickers10m 如果您重新阅读我的答案,将代码放在实例方法中不会改变与A(或B)相关的任何内容,它只会创建Main 的实例可用于C。您仍然需要手动捕获A 的实例。 @snickers10m 无需道歉,这是奇怪的代码。请永远不要使用它。 但是为什么呢?我认为它非常优雅。当然,一开始很难理解,但与将封闭实例存储在变量中相比,它很简单。 如果它难以阅读/理解,那么它对任何人来说都是不优雅的,只有一个人:作者。【参考方案3】:

根据提供的信息,我会这样做:

public class B 
    protected A getOuterInstance() 
        return A.this;
    

C继承并使用这个方法。我知道你不喜欢这种方法,但这是我能看到的最简单的答案。有了更多信息,我可能会提出一个尝试不涉及任何内部类的设计,因为这不是内部类的正常用例。

【讨论】:

并不是我不喜欢这个解决方案,只是它是一种解决方法而不是修复方法。它没有解决有关封闭实例在范围内不可用的错误,它只是回避了它。我认为发布此答案对您有好处-这将对人们有所帮助。但是,我不会让它成为公认的答案,因为它只是一种解决方法。

以上是关于为啥扩展内部类的本地类不能访问内部类封闭实例?的主要内容,如果未能解决你的问题,请参考以下文章

内部类访问外部类的变量必须是final吗,java静态方法中不能引用非静态变量,静态方法中不能创建内部类的实例

5.7 内部类

在静态方法中new 一个内部类对象和new 一个外部类对象的区别

java内部类可以访问外部类的静态方法吗

Java中的get()和set()

内部类之静态内部类