不该被忽视的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,那么随着时间的积累,代码量的增多,可能大家也可以作为第三方提供高质量的类库。鸡汤到此结束!

posted @ 2016-07-26 09:03  forget406  阅读(6067)  评论(2编辑  收藏  举报