No Enclosing instance ...的原因和解决方法

时间:2022-12-29 09:17:10

      一个月前刚考研复试完,终于也从一个毫不相关的专业转了码。现在在毕设期间找机会摸鱼学java。由于机试用的C,之前又没有接触过任何OOP语言,对Java的学习一度走了不少弯路。曾经写的沙雕代码也犯了不少错误(现在仍然在每天生产沙雕Java代码)。比如下面这段:

public class Main {
    int a = 1;
    public class Test{
        public void test() {
            System.out.println("Hello World");
        }
    }
    public static void main(String[] args) {
        Test T = new Test();
T.test(); } }

     编译器提示No enclosing instance of type Main is accessible. 也就是说没有Main的实例。查相应的博客,一群人复读“静态类方法无法调用动态实例方法,需要把类修饰符改为static”,但详细的原因却很少有人去说明,这一度让我非常难受,问题也没得到解决,只得暂时搁置。

     直到最近正在读《Java核心技术卷一》,才明白错误原因。下面进行相应的解释:

     为了搞清这个问题,要明白两个方面的知识:

1) public static void main方法

2) 内部类

     先解决第一个问题。首先我们要直到,任何的类都可以自定义一个main方法。在程序运行过程中只会调用唯一一个main方法,具体调用哪个就要看我们要加载哪一个类了。这样我们也可以明白为什么牛客OJ要求把主类名定义为Main,因为OJ仅仅测试Main中的main方法的输出结果,而其他的类中的main方法是不会被加载的。这种设计对于我们进行不同模块的单元测试是十分有利的,只需要把测试代码写到不同类的main方法中,调试时加载相应类即可。

     虽然main方法是属于类的,但当执行main方法的时候,main方法所属的类刚开始并没有被加载到内存中(先不考虑静态类),必须要在代码中new一个Main类,才会把Main的内容加载到内存中。

     这样我们就可以明白为什么错误示例代码中编译器提示没有加载Main实例了。由于没有加载Main实例,自然没有方法生成Test类,更不可能调用Test类中的方法了。因此我们实例化Main类,做以下修改:

public class Main {
    int a = 1;
    public class Test{
        public void test() {
            System.out.println("Hello World");
        }
    }
    public static void main(String[] args) {
        Main m = new Main();
        m.test();
    }
}

      emmmm.......编译器的报错更离谱了,啥都报了。这是因为第二个问题:内部类。

     下面解决第二个问题。内部类是Java的一种语法规则,也就是说,允许在类中定义类(之前写这段demo时不知道这个知识点)。很明显,Test类是在Main类中定义的内部类。而内部类的对象并不是在外部类实例化的时候就完成实例化的,也必须需要在代码中完成内部类的实例化:

        Main M = new Main();
        Main.Test T = M.new Test(); //new关键字在.后面,小心别看错了
        T.test();

    这样我们完成了Main类的实例化和Main类的内部类Test类的实例化,终于可以调用Test类的test()方法了,修改后可运行的代码如下:

public class Main {
    public class Test {
        public int a = 1;
        public void test() {
            System.out.println("Hello World");
        }
    }
    public static void main(String[] args) {
        Main M = new Main();
        Main.Test T = M.new Test();
        T.test();
    }
}

  

      这样问题就解决了。但是显然,内部类并不是我们想要的结果,它看起来太难受了,如何优雅的解决这个问题呢?

      第一种方法,将Test类修饰为static,即将Test类变为静态类也就是大多数人复读的方法在这种情况下,运行main方法时,虽然并没有实例化Test对象,但是Test类的静态方法和静态属性已经加载到内存中了,自然可以直接使用,不会提示No Enclosing XXX的内容。

public class Main {
    public static class Test {
        public int a = 1;
        public void test() {
            System.out.println("Hello World");
        }
    }
    public static void main(String[] args) {
        Test t = new Test();
        t.test();
    }
}

      第二种方法更简单,将算法的实现方法和操纵的类分开定义,将包含算法的main定义在Main类中,将类定义在另一个class中,这样我们直接选择实例化Test类就可以了,相应的修改代码如下:

public class Main { // 在被加载的类Main中定义算法过程
    public static void main(String[] args) {
        Test T = new Test();
        T.test();
    }
}

class Test {   //把使用的类拿到外面定义
    public int a = 1;
    public void test() {
        System.out.println("Hello World");
    }
}