【问题标题】:"no enclosing instance of type file1 is in scope" error with inner classes“范围内没有 file1 类型的封闭实例”内部类错误
【发布时间】:2021-03-13 22:31:15
【问题描述】:

我有一些代码(本质上它导入了这个其他类,它有一个我正在尝试访问的内部类),但它带来了这个错误:

file2.java:5: error: no enclosing instance of type file1 is in scope
    public static class file3 extends file2.Inner {

“范围内没有 file1 类型的封闭实例”是什么意思?

代码如下:

package test;
import secret.file1;

public class file2 extends file1 {
    public static class file3 extends file2.Inner {
        public static void main(String[] args) {
            file3 outer = new file3();
            System.out.println(outer.x);
        }
    }
}

file1 的代码:

package secret;

public class file1 {
    protected class Inner {
        public int x = 8;
    }
}

【问题讨论】:

  • 我承认我不是 100% 确定错误消息,但我发现您的代码存在 2 个问题:1) 您没有定义名为 Inner 的嵌套静态类,所以你怎么能延长它?您的 IDE 应该在 extends file2.Inner 上发出危险信号。 2)您缺少主要方法。您在file3 中有一个名为main() 的方法,但主方法的定义特征之一是它必须位于顶级类中,因此Java 不会将其识别为主方法。跨度>
  • @user15358848 没错,我在考虑内部类,但file3 是嵌套类,而不是内部类。我的错。
  • @user15358848,我将其更改为 extends file1.Inner 但这似乎并不能解决错误。如果有任何帮助,我还编辑了帖子以包含 file1 类

标签: java inheritance inner-classes


【解决方案1】:

仅用于教育目的,请勿在实践中使用。

内部类具有这种独特的行为,其中所有构造函数隐式在第一个位置声明封闭类型的参数。所以,

public class file1 {
    public class Inner {
    }
}

实际上被编译成类似的东西

public class file1 {
    public class Inner {
        public Inner(file1 enclosing) {
        }
    }
}

其中enclosing 实际上是对其初始化的封闭Outer 实例的引用。

new file1().new Inner();
// compiles to something like
file1 enclosing = new file1();
new Inner(enclosing);

请记住,在常规继承中,如果超类声明了参数化构造函数(并且没有无参数构造函数),则子类必须使用super(...) 在自己的构造函数中调用它。通过内部类的继承,如果还隐式提供了封闭实例,则可以隐式完成super(...)调用。

也就是说,如果file3 实际上也是file1(或file2)的内部类,而不是嵌套类,那么您的类层次结构就会被编译。

public class file2 extends file1 {
    public class file3 extends file2.Inner {
    }
}

正如我之前所说,file3 将编译为类似

public class file3 extends file2.Inner {
    public file3(file2 enclosing) {
        super(enclosing); // this is calling the `public Inner(file1)` constructor
    }
}

super(..) 调用将隐式完成,因为“file1 类型的封闭实例在范围内”。

但是,由于您的 file3 类不是 file1 的内部类,也不是 file2,因此没有 implicit 封闭 file1 实例以传递给超级构造函数,因此Java 编译器会产生您看到的错误。

我一直将隐式这个词加粗,以试图强调Java允许您显式绕过所有这些的事实。此功能是内部类独有的。 Java 提供以下语法来声明带有参数(在任何位置)的构造函数,以显式将其视为封闭实例

public static class file3 extends file2.Inner {
    public file3(file2 enclosing) {
        enclosing.super(); // special syntax
    }
    // [...]
}

而且,就像常规继承一样,super(..) 调用必须是构造函数主体中的第一条语句。

你的 main 方法然后变成类似

file2 file2 = new file2();
file3 outer = new file3(file2);
System.out.println(outer.x);

您明确提供file2.Inner 所需的封闭实例(也可以通过null)。


为了您的信息,还有语法可以显式声明我在第二个 sn-p 中提到的构造函数,参数表示封闭实例

public class file1 {
    public class Inner {
        public Inner(@SomeAnnotation file1 file1.this) {
        }
    }
}

这真的只有在你想注释参数时才有用。

此信息在Chapter 8 of the Java Language Specification 中传播,特别是在有关“内部类”和“接收器参数”的部分中。

【讨论】:

    【解决方案2】:

    问题

    您试图使嵌套类成为内部类的子类,而这实际上是不可能的。有一种方法可以做到这一点,请参阅 Savior 的语法答案,但在您的情况下,它对您没有帮助。嵌套类和内部类之间的主要区别在于,内部类与外部类的特定实例相关联,而嵌套类不与任何特定实例相关联。为了说明这种差异,让我们稍微修改一下您的代码:

    public class file1 {
        private int x;
    
        public file1(int x) {
            this.x = x;
        }
    
        // No static keyword, so this is an inner class:
        protected class Inner {
            public void printXInner() {
                // This is legal:
                System.out.println(x);
            }
        }
    
        // Has static keyword, so this is a nested class:
        protected static class Nested {
            public void printXNested() {
                // This is NOT legal:
                System.out.println(x);
            }
        }
    }
    

    printXInner() 方法是完全合法的,因为Inner 绑定到file1 的特定实例,因此它可以访问实例特定(非静态)字段x。但是,printXNested() 方法会抛出编译时异常,因为它不知道您要使用哪个 x。您可能有很多 file1 实例,每个实例可能有不同的 x 值。

    现在,为什么这很重要,它与您的代码有什么关系?好吧,假设 Java 刚刚决定允许您尝试做的事情;您可以扩展内部类。如果我们仍然使用上面修改过的代码,我们可以这样写:

    public class file2 extends file1.Inner {
        public void printXFile2() {
            printXInner(); // Uh oh, what should the program do here?
        }
    }
    

    这段代码看起来完全无害,它扩展了Inner 并调用了printXInner() 方法。除了我们的老问题再次出现:which x 应该打印吗?我们还没有指定使用哪个 file1 实例。因此,您实际上是在尝试在静态上下文中使用内部类。


    现在,希望您在想“好吧,我明白为什么会出现问题,但我该如何解决呢?”您基本上有两个选择:您可以将内部类更改为嵌套类,您可以将 file2 类更改为内部类。您选择哪个选项将取决于您的代码结构。您需要决定是否要将 Inner 绑定到 file1 的特定实例。

    修复 #1

    将内部类更改为嵌套类。如果您不需要从嵌套类访问特定于实例的数据,这就是要走的路。也许你有一个汽车类,你想有一个轮子的嵌套类。如果我们在汽车零件商店的意义上谈论这个,轮子没有附加到任何汽车上,所以我们不希望我们的嵌套类附加到外部类的任何实例。只需像这样修改file1

    public class file1 {
        // Added static keyword:
        protected static class Nested {
            public int x = 8;
        }
    }
    

    修复 #2

    使子类扩展超类的特定实例的内部类。如果您需要从内部类访问特定于实例的数据,这是要走的路。也许你有一个汽车类,你想有一个轮子的内部类。如果我们在驾驶模拟器的意义上谈论这个,那么车轮连接到特定的汽车上并且它们保持连接状态。因此,我们希望我们的内部类附加到外部类的特定实例。只需像这样修改file3

    public class file2 extends file1 {
        // Removed static keyword:
        public class file3 extends Inner { ... }
    }
    

    不过,这可能会造成次要问题。内部类中不能有 main 方法,因为在启动时,没有外部类的实例,所以 JRE 不能调用内部类的方法。一种解决方法是将主方法放在外部类中,并从那里调用特定实例的方法。你可以这样实现:

    public class file2 extends file1 {
        public static void main(String[] args) {
            new file2().file3.doSomething();
        }
    
        public class file3 extends Inner {
            public static void doSomething() {
                file3 instance = new file3();
                System.out.println(instance.x);
            }
        }
    }
    

    TL;DR

    内部类不同于嵌套类。内部类绑定到外部类的特定实例,而嵌套类不绑定到任何特定实例。你不能在不指定实例的情况下引用内部类,因为这违背了内部类的定义。

    【讨论】:

    • @Savior 好的,我已经阅读了您的答案,您是对的,有语法支持扩展内部类的嵌套类。也就是说,我不明白你为什么要使用它。通过要求封闭实例参数,您要求file3 绑定到file2 的实例,如果您刚刚将file3 设置为内部类,就会发生这种情况。当您可以通过删除 static 关键字来实现完全相同的结果时,为什么还要编写所有额外花哨的代码呢?如果file2 不是file1 的子类,情况会有所不同。
    • @Savior 我已将其称为对第一句话的警告。
    猜你喜欢
    • 2014-04-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多