折磨人的小妖精---main
方法
真的有公司会这么变态,用main
方法来折磨你吗?你得承认,的确有比较low的公司会这么做。本文对这些与main
方法相关的问题做一个小结。
1、下列程序运行结果是 public class Hello { public static void main(String s) { System.out.println("Hello"); } } A.编译错误 B.运行输出 "Hello" C.编译无错,但运行时指示没有定义构造方法 D.编译无错,但运行时指示没有正确定义main方法
编译器只检查语法问题(包括检查异常,Checked Exception),如果没有语法问题,编译器会将源程序编译成字节码文件,由虚拟机在运行时对字节码进行解读,如果运行时程序无法正常工作,则JVM可能会抛出异常提示信息。
public static void main(String s)
,编译器检查其并没有任何语法错误,不过运行时会抛异常。
下面对main方法涉及到的知识点笼统地概括一下。
(1)访问权限(访问修饰词):只要符合语法规定的访问方法的权限,均能够通过编译。比如,private
、默认、protected
、public
,都能通过编译器的检查。但是,运行时只有public
不会被抛出异常,而且规定必须加public
。
(2)限定修饰词:只要是符合语法规范的限定修饰词,均能够通过编译。比如,abstract
(此时所在类必须声明为abstract
)、final、static、synchronized、native
,都能够通过编译器的检查。但是,对于JVM而言,如果类中不存在static
关键词是无论如何无法运行的。在static
必须存在的情况下,可以加上final
(表明不能被重写),synchronized
(表明同步),这两个在运行时均正常,不抛异常信息。不过,在static
存在的情况下加abstract
(static
不存在时如果abstract
和final
同时用也有问题),无法通过编译,原因是修饰词造成互相矛盾。
(3)返回值类型:只要符合语法规范的返回值类型均能够通过编译。注意到,如果是非void
类型,必须加return
语句;如果是void
,可以加上
,也可以不加。但是,只有void在运行时不抛异常,即使加上return;
也不抛出异常。return
;
(4)参数列表(参数类型,参数名):参数类型必须是String[],参数名只要是合法的标识符即可,不一定要写成args(约定俗称这么写,但Java语言规范没明确要求)。
针对这些知识点,下面列出相应的题目,仅供参考。
变式1: public class Hello { private static void main(String[] s) { System.out.println("Hello"); } } 编译通过(会有警告,仍能通过编译),但运行时找不到main方法。 变式2: public class Hello { public static int main(String[] s) { System.out.println("Hello"); return 0; } } 编译通过,但是运行时提示main方法返回值必须为空值型;
变式3: public class Hello { public static void main(String 1$) { System.out.println("Hello"); } } 编译器检查到语法错误,无法通过编译。Java的标识符只能是字母、数字、下划线、美元符号组合,且不能以数字开头。当然变量名还能够是中文字符等,只不过我们不提倡。 变式4: public class Hello { public static void main(String[] s) { System.out.println("Hello"); return; // 符合语法规范。void作为返回值时,可以使用return;使方法弹出栈帧。 } } 通过编译,运行时输出Hello 变式5: public class Hello { public static final void main(String[] s) { // final关键词表明main方法无法被子类覆盖 System.out.println("Hello"); return; } } 通过编译,运行时输出:Hello 变式6: public abstract class Hello { // 抽象类允许其中有非抽象方法 public static final void main(String[] $) { System.out.println("Hello"); return; } } 编译正确,运行时输出Hello 变式7: import java.io.Serializable; public abstract class Hello implements Cloneable, Serializable { public static final void main(String[] $) { System.out.println("Hello"); return; } } 编译正确,运行时输出Hello 分析:Cloneable,Serializable都是标记接口(Tagging Interface) 变式8: import java.io.Serializable; abstract class Hello implements Cloneable, Serializable { public static final void main(String[] $) { System.out.println("Hello"); return; } }编译正确,输出Hello分析:类的访问修饰符可以是public和默认两种,而且写在一起的多个类,至多只能有一个public类。 变式9: public class Hello{ static { System.out.print("Hello"); } public static final void main(String[] $) { System.exit(0); } static { System.out.print(",World"); } } 编译正确,输出Hello,World。 注意:静态块在main方法调用之前载入。 变式10: public class Hello{ { System.out.println("Hello"); } public static void main(String[] $) { System.exit(0); } static { System.out.println("world"); } }编译正确,输出world。 载入顺序:静态块,main方法(静态方法),普通块。但是在main方法时已经退出程序,因此普通块不执行。
变式11:public class Hello { public static synchronized void main(String[] 我) { System.out.println("Hello World"); return; } }编译正确,输出Hello World分析:允许在main方法中加入同步修饰词synchronized。Java语法没有规定不可以使用中文字符的,只是不推荐使用中文。
这里只是总结了部分关于main
方法的注意点,还有许多细节内容没有总结到,比如不用main方法如何定义一个类?
等等的问题。
《10个经典的Java main方法面试题》、《不该被忽视的CoreJava细节(一)》
啰嗦一下:为什么
main
方法必须使用static
关键词?
main方法实际上储存的位置是运行时常量池。在程序运行的时候,HelloWorld.class从运行时常量池被类装载器(ClassLoader)加载到虚拟机运行时常量池(方法区),然后再加载HelloWorld.class中需要导入的Java API和相应的静态资源。当核心类库API和除main外静态资源都加载完毕,虚拟机开始寻找HelloWorld.class中的main方法,如果成功找到需要的main方法,那么程序正是开始执行。
如果main方法不是static,那么虚拟机将无法构造运行时的诸多结构。因为运行时环境产生的前提是main方法正确加载,然后执行main方法中包含的内容。可以这么说,main方法是连接静态环境和运行时环境的桥梁。
本文关于main方法的内容几乎就是我所能找到的所有内容了,如果再要补充,获取只能够从底层字节码方面去研究了。这些都是将来的事,现在的确没有这么多闲工夫。
同现实的落差---记录心情
我所接收的教育、学习经历、性格秉性等等决定了我不喜欢教条主义(dogmatism)式的问题,而且有些东西不是说懂了就一定能在面试这种场合通过严密的逻辑向提问方展示。如果你想考验我是否懂了一个知识,可以以文章的形式告诉你我确实懂了,但是我很难接受人家非要在面试这种场合让我回答很书面化的内容。
比如,人家问我Override和Overload区别?我觉得我真的一时间无法回答完整,而且语言组织确实不行。但是如果是通过手写回答,我觉得可以答得很完美。
不管怎么说,只能去适应别人,适应社会的大潮,总不能让面试官来适应我吧,毕竟人家可能是技术大牛,而我只是菜鸟而已。