一个月前刚考研复试完,终于也从一个毫不相关的专业转了码。现在在毕设期间找机会摸鱼学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"); } }