不该被忽视的CoreJava细节(一)
一、系列文章导言
《不该被忽视的CoreJava细节》系列文章将会持续更新。我希望自己通过这一系列文章的写作,能与读者一起进步,逐步完善对Java体系结构的了解。
二、本期关注点
几乎翻看每一本与Java相关的入门书籍,教你跟着敲的第一个程序都会像下面这段代码一样。
1 public class HelloWorld { 2 public static void main(String[] args) { 3 System.out.println("Hello World"); 4 } 5 }
这个程序貌似很简单
,很多人都不以为意。但是,请大家扪心自问一下,你真的有花时间研究过吗或者说真的理解了吗?
不过,请你大可放心,本文不是教你编译器编译这段程序机制,也不是教你字节码反编译后如何解读,更不会教你JVM如何处理这段程序以及类的装载机制。
本期只是想让大家从最基础的角度读懂
public static void main(String[] args) { }
这段代码,仅此而已。
三、main访问限定词趣闻
根据Java语言规范,main
方法必须声明为public
。注意,Java语言是Sun公司对于Java语言的官方文档,在Java语言方面具有最高的权威性。
但是,Java虚拟机规范并没有要求main
方法一定是public
。
两者的迥异导致,有些Java解释器能够执行非public
修饰main
方法所在的程序。
不过,这个bug最终在JDK1.4中得到了修复,其后的所有main
方法必须使用public
作为访问修饰符。
下面我尝试使用JDK1.7编写此方法,将main
方法的public
修改成private
。
上图说明,至少在JDK1.7版本,这个bug已经被修复,无法使用非public
修饰词修饰main
方法。而且,我可以明确地告诉大家,1.7的虚拟机规范仍然没有指明必须这样做。
四、static
修饰词、void
返回值对main
意味什么
先拿void
开刀。如果在学过Java之前学过C语言,那么你一定会想起每次写main方法的时候都会写上这么一段话。
1 int main() { 2 ... 3 return 0; 4 }
表示程序会给操作系统返回一个状态码0,操作系统收到状态码0表示程序正常退出。
但是,我们的Java语言并没有返回值。也就是说,理论上Java程序并不会主动返回退出代码。
那么到底Java程序有没有返回状态代码呢?事实上,如果Java程序正常退出,则会返回默认的退出码0。
1 public class HelloWorld { 2 public static void main(String[] args) { 3 System.out.println("Hello World"); 4 } 5 } // 程序正常退出,退出码如下图。
通过操作系统的errorlevel变量我们可以得到程序所返回的退出码。关于Java退出码需要再了解四点:
1)Java程序的退出码并不会返回给JVM,这也就是方法用void
的原因所在。
2)Java程序的退出码返回给JVM所在的操作系统,正常退出默认返回给操作系统状态码值为0。
3)退出码其实是约定俗成的。一般约定[0,99]内整数代表正常退出,[100-199]代表警告退出,大于等于200代表异常退出,但是不同的操作系统却不同。在Java中,默认的正常退出码为0,异常退出或警告码为1。
1 public class HelloWorld { 2 public static void main(String[] args) throws ClassNotFoundException{ 3 System.out.println("Hello World"); 4 Class.forName(""); 5 } 6 } // 不声明异常,声明异常,退出码均为 1
4)可以自己设置退出状态码。Java调用java.lang.System.exit(int status)
方法结束程序执行,并向操作系统返回status
值。
1 public class HelloWorld { 2 public static void main(String[] args) { 3 System.out.println("Hello World"); 4 System.exit(1000); 5 } 6 } // 向JVM所在操作系统返回自定义状态码
顺带提一句,如果在eclipse做实验并不会有状态码,或者说在eclipse跑程序,在命令行中状态码始终未0。其中差别始末,暂时未考虑清楚。
五、参数列表--变态考题
如果要出考题,前面的内容或许都用不到,基本没有什么可以出的题目。但是一提到main
方法的参数列表,就有意思了。下面我就几个问题解答一下诸位可能存在的疑问。
1)public static void main(String[] s) {}
是否正确?
咦,貌似和原来的有点区别,好像原来的形参是args
。不明真相的群众可能一口咬定这句话是错误的,不巧的是,这句话确是没有任何无法问题的。
1 public class HelloWorld { 2 public static void main(String[] s) { 3 System.out.println("Hello World"); 4 } 5 }
这段代码能够正确地输出结果。其中的原因在于:原来的args
仅仅是参数,编译器是不会检查形参的变量名是否与args
一致的,只要有一个合法的名字,编译器就认为没有问题,运行也不会出错。更进一步说,这个参数就是在控制台输入参数后用于获得参数用的,无非就是数组名字改了而已。
如果编译程序的时候加上参数,比如javac HelloWorld.java 1 2 3
1 public class HelloWorld { 2 public static void main(String[] s) { 3 System.out.println("Hello World"); 4 System.out.println(s[0]); // 输出结果为1 5 } 6 }
2)public static void main(String args)
是否正确?
这句代码编译器不能通过编译,缺少程序入口。
3)public static void main(Char[] args)
是否正确?
这句代码编译器不能通过编译,缺少程序入口。
上面三种类型说明,必须存在《code>public static void main(String[] 任意合法变量标识符) {}方法。这也是Java官方文档所规定的,必须遵守。
六、奇怪,怎么有2个main
方法?
为什么就不可能存在多个main
方法?请看下面的例子。
1 public class HelloWorld { 2 public static void main(String[] args) { 3 main("Hello World"); 4 } 5 6 public static void main(String arg) { 7 System.out.println("Hello World"); 8 } 9 }
本例是通过函数的重载实现的。其实很容易想到,在同一个类中有多个相同方法名存在很可能是使用了Java重载机制(编译器只检查方法名和参数列表,如果存在不同,则认为是两个不同的方法,编译器认为语法成立)。
如果,一不小心写成下面这种样子,则会出现StackOverflowError
异常,本质上是虚拟机方法栈帧存不下无穷递归过程的数据量。
1 public class HelloWorld { 2 public static void main(String[] args) { 3 main("Hello World"); 4 } 5 // 无递归出口,导致无穷递归 6 public static void main(String arg) { 7 main("Hello World"); 8 } 9 }
七、结束语
读完文本,是否觉得自己原来自己还有很多漏洞没有弥补。别慌,我想说的是:Java只是一种工具,一种为我们服务的编程工具。人与工具之间的关系不应该主次颠倒,应该尽量让工具为我们写出高质量应用而服务,而不是在这已经发展了20多年的工具面前如同乞讨者一般盲目仰慕前辈做出的巨大贡献。
其实Java也就那样,掌握语言的核心知识,学点大牛写的API,那么随着时间的积累,代码量的增多,可能大家也可以作为第三方提供高质量的类库。鸡汤到此结束!