HelloWorld,学习每门语言的第一步。有人戏称,这些年的编程生涯就是学习各种语言的HelloWorld,不知是自谦还是自嘲。目前所在的公司使用Java作为主要开发语言,我进行语言转换也大半年了,这HelloWorld便是语言转换的第一关。好在本科的时候学过那么一点,而且在此之前进行了较长时间的C/C++开发,其间有不少的相似之处。这里略去JDK的安装和环境配置(JDK为1.6.0.45),直接从代码入手。

  首先看一个最简单的Java下的HelloWorld:

public class HelloWorld {
public static void main(String[]agrs)
{
System.out.println("HelloWorld!");
}
}

  一般来说,初学者写HelloWorld到这里,编译完运行一下看到结果就可以结束了。下面对这个小程序进行更多的探索,进一步了解和学习Java编程中的特性。

1.源码文件的编码

  最初为了简单起见,我是在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方法中指定编码,这里不再做一步的详述。

2.为什么没有import语句?

  还记得经典的K&R中经典的HelloWorld么?即使极尽精简,C中仍然避免不了使用#include <stdio.h>来引入头文件,才能使用printf函数。

  而Java和C/C++不一样,这个简单的HelloWorld不需要类似include的import,也不需要使用命名空间,看似更简单了些。实际上,这是因为Java给每个Java文件都默认导入了java.lang这个包,从而省去了import java.lang;这个语句罢了。这样,下面进行屏幕输出直接使用System.out.println()即可。

  java.lang中包括的都是常用的类和方法,具体内容读者有兴趣可以自行查阅。上文提到java.import是“默认导入”,有没有什么办法禁止其导入?我搜索了下,目前还没有查到相关的资料,如果哪位读者了解,希望能告诉我。(这可能涉及到类加载器的问题,暂未进行研究)

  如果你执意在这段简单的代码中使用与import对应的package,可以参考本文第六节

3.文件名为什么要与类名一致?类名与修饰符问题

  在实践中可以看出,编译结果是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()方法。

4.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]是执行的程序名。 

5.既然main()方法是类方法……

  既然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被玩坏了,这个无限递归创建对象的过程导致了内存溢出。

6.试试package

  当然,使用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)

相关阅读

深入理解Java HelloWorld

Java入门记(一):折腾HelloWorld的更多相关文章

  1. Java入门记(五):容器关系的梳理(下)——Map

    注意:阅读本文及相关源码时,需要数据结构相关知识,包括:哈希表.链表.红黑树. Map是将键(key)映射到值(value)的对象.不同的映射不能包含相同的键:每个键最多只能映射到一个值.下图是常见M ...

  2. Java入门记(四):容器关系的梳理(上)——Collection

    目录 一.Collection及子类/接口容器继承关系 二.List 2.1 ArrayList 2.1.1 序列化的探讨 2.1.2 删除元素 2.1.3 调整大小 2.2 Vector和Stack ...

  3. Java入门记(三):初始化顺序

    初始化顺序的规则 1.在一个类的对象实例化时,成员变量首先初始化,然后才调用构造器,无论书写顺序.如果调用构造器前,没有显式初始化,那么会赋默认值. 这样做法的原因可以理解为:构造器执行时可能会用到一 ...

  4. Java入门记(二):向上转型与向下转型

    在对Java学习的过程中,对于转型这种操作比较迷茫,特总结出了此文.例子参考了<Java编程思想>. 目录 几个同义词 向上转型与向下转型 例一:向上转型,调用指定的父类方法 例二:向上转 ...

  5. JAVA入门第一季(mooc-笔记)

    笔记相关信息 /** * @subject <学习与创业>作业1 * @author 信管1142班 201411671210 赖俊杰 * @className <JAVA入门第一季 ...

  6. JAVA入门第二季(mooc-笔记)

    相关信息 /** * @subject <学习与创业>作业1 * @author 信管1142班 201411671210 赖俊杰 * @className <JAVA入门第二季&g ...

  7. Java入门-类HelloWorld是公共的,应在名为HelloWorld.java的文件中声明

    开始学习java了,搭好环境,notepad++中新建一个java文件,新建一个HelloWorld类, public class HelloWorld { public static void ma ...

  8. java 入门-helloWorld

    Java 教程 Java 是由Sun Microsystems公司于1995年5月推出的高级程序设计语言. Java可运行于多个平台,如Windows, Mac OS,及其他多种UNIX版本的系统. ...

  9. Java入门(二)——果然断更的都是要受惩罚的。。。

    断更了一个多月,阅读量立马从100+跌落至10-,虽说不是很看重这个,毕竟只是当这个是自己的学习笔记,但有人看,有人评论,有人认同和批评的感觉还是很巴适的,尤其以前有过却又被剥夺的,惨兮兮的. 好好写 ...

随机推荐

  1. Nuke

    - Debugging python code IN nuke with Eclipse - Documents: http://www.thefoundry.co.uk/products/nuke- ...

  2. Redis 简单命令

    1. 新增 set keyName "keyValue" 2. 获取 get keyName 查看所有Key keys * 3. 删除 //删除当前数据库中的所有Key flush ...

  3. Unity关于一个UGUI优化效率的方法

    无意间发现了一个小技巧.如下图所示,可以发现UGUI的Image组件的RaycastTarget勾选以后会消耗一些效率,为了节省效率就不要勾选它了,不仅Image组件Text组件也有这样的问题. 一般 ...

  4. C#如何获取CPU处理器核心数量 z

    有几条不同的处理器信息,您可以获得有关的信息:物理处理器数量.核心数量和逻辑处理器数量,这些可以不同.两颗双核超线程(启用)处理器的机器情况下有:2个物理处理器.4个核心和8个逻辑处理器. 逻辑处理器 ...

  5. iis网站发布相关问题

    最近在公司的服务器上发布了一个简单的web应用,整个做下来到上线用了将近2天时间,期间出现了各种问题,现在发出来供大家参考: 1.iis上发布后出现访问网站,出现“IIS服务器被配置为不列出此目录的内 ...

  6. ASCIITable: 演示 Arduino 串口输出的进阶功能

    原文地址 - https://www.arduino.cc/en/Tutorial/ASCIITable ASCII字符表 本例展示了高级的串口打印功能,通过本功能可以在Arduino软件(IDE)的 ...

  7. (Array,位操作)137. Single Number II

    Given an array of integers, every element appears three times except for one. Find that single one. ...

  8. 帝国cms 无法生成静态页

    建立目录不成功!请检查目录权限 安装完了帝国网站管理系统(帝国CMS)之后,没有进行初始化内置数据的步骤,而引起的错误.其实只要你按照安装说明一步一步下来就不会出错. 解决办法:初始化内置数据 帝国c ...

  9. Why is Visual Studio 2015 not able to find or open PDB files?

    first change parameters, Tools->Options->Debugging->Symbols->Microsoft Symbol Server, ye ...

  10. LeedCde 题解目录

    1. Longest Palindromic Substring ( 最长回文子串 ) 2. Median of Two Sorted Arrays (两个排序数组的中位数) 3. Sqrt(x) 4 ...