HelloWorld,学习每门语言的第一步。有人戏称,这些年的编程生涯就是学习各类语言的HelloWorld,不知是自谦仍是自嘲。目前所在的公司使用Java做为主要开发语言,我进行语言转换也大半年了,这HelloWorld即是语言转换的第一关。好在本科的时候学过那么一点,并且在此以前进行了较长时间的C/C++开发,其间有很多的类似之处。这里略去JDK的安装和环境配置(JDK为1.6.0.45),直接从代码入手。html
首先看一个最简单的Java下的HelloWorld:java
public class HelloWorld { public static void main(String[]agrs) { System.out.println("HelloWorld!"); } }
通常来讲,初学者写HelloWorld到这里,编译完运行一下看到结果就能够结束了。下面对这个小程序进行更多的探索,进一步了解和学习Java编程中的特性。编程
最初为了简单起见,我是在Win7中用记事本编写并保存代码为HelloWorld.java,而后用命令行直接javac编译。出于在Windows下写Linux程序的习惯,我在记事本保存时将代码保存为UTF-8编码的HelloWorld.java文件。编译时提示:小程序
在仔细检查源代码肯定没有任何拼写错误后,尝试将编码改回Windows默认的ANSI,成功生成了HelloWorld.class并可以正确运行,看来是编码不一致惹得祸。接下来,抱着尝试的心态,使用Unicode和Unicode Big Endian保存源码,发现也会报错,只是提示不一样,编译器提示有非法字符。这个问题若是在Eclipse中用默认方式保存文件,则不会出现。函数
有趣的是,若是使用Java的I/O方法生成文本文件,应该如何肯定文件的编码,也是一个常见的问题。若是仅仅是涉及Windows/Linux两个平台之间的编码差别,而不包括中文编码,前者使用\r\n,然后者使用\n\r或\n便可。对于汉字编码,须要在使用到的I/O方法中指定编码,这里再也不作一步的详述。oop
还记得经典的K&R中经典的HelloWorld么?即便极尽精简,C中仍然避免不了使用#include <stdio.h>来引入头文件,才能使用printf函数。post
而Java和C/C++不同,这个简单的HelloWorld不须要相似include的import,也不须要使用命名空间,看似更简单了些。实际上,这是由于Java给每一个Java文件都默认导入了java.lang这个包,从而省去了import java.lang;这个语句罢了。这样,下面进行屏幕输出直接使用System.out.println()便可。学习
java.lang中包括的都是经常使用的类和方法,具体内容读者有兴趣能够自行查阅。上文提到java.import是“默认导入”,有没有什么办法禁止其导入?我搜索了下,目前尚未查到相关的资料,若是哪位读者了解,但愿能告诉我。(这可能涉及到类加载器的问题,暂未进行研究)测试
若是你执意在这段简单的代码中使用与import对应的package,能够参考本文第六节。编码
在实践中能够看出,编译结果是HelloWorld.class,可是运行的命令倒是java HelloWorld。若是这个文件还有更多的类,能够看到这些类在编译时都生成了*.class文件。对于“类名和文件名一致”这个疑问提的并不合理,显然代码编写时,一个文件中能够有不少个类。这涉及到了Java的特性(来自《Java编程思想(第四版中文版)》):
每一个编译单元(文件)只能最多有一个public类;若是有,其名称必须与含有这个编译单元的文件名相匹配,包括大小写。
若是不遵照这个要求,写出相似下面的代码
//ERROR IN CODE public class HelloWorld { public static void main(String[]agrs) { System.out.println("HelloWorld!"); } } public class HelloWorld2 { public static void main(String[]agrs) { System.out.println("HelloWorld, me too!"); } }
那么编译器会提示这一点
若是把HelloWorld2类的public去掉,将使其变成包访问权限,程序能够正常运行,此时只执行HelloWorld.main(),并不会发生冲突。
实际上,若是这个文件只有一个HelloWorld类,或者有两个类,只要这个包括main()的类名与文件名一致,类名前不加public也是能够正常运行的,且调用的是与文件名一致的类的main()方法。但我的认为这不是良好的编程实践,以下:
class HelloWorld { public static void main(String[]agrs) { System.out.println("HelloWorld!"); } } class HelloWorld2 { public static void main(String[]agrs) { System.out.println("HelloWorld, me too!"); } }
编译时将生成HelloWorld.class和HelloWorld2.class,分别运行时,结果为两个类各自的main()方法。
在C中,对于main()的修饰符和参数表有着不少细节要注意(能够参考五花八门的main())。对于Java,这里对main()的写法也进行简单的探究。
先来看参数表String args[]。虽然说编译器要求必须是这种形式,但若是不用标准形式而用其余形式如int x、String s做为参数表,编译是能够经过的,可是在执行时则会抛出异常,不管是否提供了参数:
NoSuchMethodError表名,指望的是参数为String args[]的main()方法。虽然提供了同名方法,因为方法的重载机制,并不能代替指望的main(String args[])方法。
接下来看修饰符public。在第3条已经提到了public对于类名的修饰有所说明,而对于main()这个与文件同名的类的成员方法,为了能被调用,只能用public修饰。不使用修饰符(包访问权限)、使用private或protected都会提示:
对于修饰符static,代表这个方法是在存储在静态存储区的,不须要实例化对象就能够调用。去掉static后,能够编译经过,运行时提示
为了进一步验证这一点,能够编写构造方法来验证。(构造方法是在类的对象在实例化时会被调用的方法)
public class HelloWorld { HelloWrold { System.out.println("Constructor"); } public static void main(String[]agrs) { System.out.println("HelloWorld!"); } }
编译运行时,能够看到构造方法并无运行。
对于修饰符void,也是必须的。改为int等并加上对应的return语句一样会提示“NoSuchMethodError: main”。在《Java虚拟机规范(JavaSE7)》(周志明等译)中介绍到
Java虚拟机的启动是经过引导类加载器(Bootstrap Class Loader §5.3.1)建立一个初始类(Initial Class)来完成,这个类是由虚拟机的具体实现指定。紧接着,Java虚拟机连接这个初始类,初始化并调用它的public void main(String[])方法。以后的整个执行过程都是由对此方法的调用开始。
可见,void返回值也是被要求的,其余形式是不容许的。
通过进一步的测试可知,args[0]是第一个参数;而在C中,argv[0]是执行的程序名。
既然main()方法是类方法,那么在实例化这个类的对象时,天然能够再次调用这个方法。对HelloWorld源代码加对应的两行,以下所示
public class HelloWorld { public static void main(String[] args) { HelloWorld h = new HelloWorld(); System.out.println("HelloWorld!"); h.main(args); } }
运行结果为
HelloWorld!
HelloWorld!
HelloWorld!
... ...
HelloWorld!
Exception in thread "main" java.lang.StackOverflowError
at sun.nio.cs.ext.DoubleByteEncoder.encodeLoop(Unknown Source)
at java.nio.charset.CharsetEncoder.encode(Unknown Source)
at sun.nio.cs.StreamEncoder.implWrite(Unknown Source)
at sun.nio.cs.StreamEncoder.write(Unknown Source)
at java.io.OutputStreamWriter.write(Unknown Source)
at java.io.BufferedWriter.flushBuffer(Unknown Source)
at java.io.PrintStream.write(Unknown Source)
at java.io.PrintStream.print(Unknown Source)
at java.io.PrintStream.println(Unknown Source)
at main.HelloWorld.main(HelloWorld.java:15)
at main.HelloWorld.main(HelloWorld.java:16)
at main.HelloWorld.main(HelloWorld.java:16)
at main.HelloWorld.main(HelloWorld.java:16)
... ...
可见HelloWorld被玩坏了,这个无限递归建立对象的过程致使了内存溢出。
固然,使用java更多的时候每每要处理多个文件。为了组织同一命名空间下的文件,须要使用包来进行。对应于import,为了指定当前文件在哪一个包,须要加上package语句。随便加上一个包名,最初的代码变成了
package test; public class HelloWorld { public static void main(String[]agrs) { System.out.println("HelloWorld!"); } }
编译后,却没法运行,以下图所示
其实,包名是隐含目录结构的。为了运行,须要把HelloWorld.class移入这个路径的test文件夹,按照下面的方式运行才能够:
(2015.10.6更新)若是引用了三方jar包,能够在运行javac和java命令的时候使用-cp指定jar包所在相对路径,或者直接把jar包放在该class文件所在目录或环境变量CLASSPATH指定的目录下。
可见,对于一个小小的HelloWorld,仍是有很多东西能够发掘,只是限于篇幅和本人水平,本文仅仅进行了简要的介绍。如下是本文提出的能够在后续学习中继续深刻的主题,仅供参考:
1.I/O方法编码方式的选择
2.包和代码组织
3.Java虚拟机(JVM)