Java入门记(一):折腾HelloWorld

  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语句。随便加上一个包名,最初的代码变成了

import test

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

编译后,却无法运行,如下图所示

  其实,包名是隐含目录结构的。为了运行,需要把HelloWorld.class移入这个路径的test文件夹,按照下面的方式运行才可以:

小结

  可见,对于一个小小的HelloWorld,还是有不少东西可以发掘,只是限于篇幅和本人水平,本文仅仅进行了简要的介绍。以下是本文提出的可以在后续学习中继续深入的主题,仅供参考:

1.I/O方法编码方式的选择

2.包和代码组织

3.Java虚拟机(JVM)

相关阅读

深入理解Java HelloWorld

时间: 2024-12-25 06:52:59

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

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

注意:阅读本文及相关源码时,需要数据结构相关知识,包括:哈希表.链表.红黑树. Map是将键(key)映射到值(value)的对象.不同的映射不能包含相同的键:每个键最多只能映射到一个值.下图是常见Map的接口和实现.与Collection相比,继承关系简单不少. 一.Map接口和AbstractMap抽象类 Map接口除了增加映射.根据key获取value.判断映射中的key或value是否存在.删除映射的基本方法外,还包含了返回包含所有key的Set.包含所有value的collection

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

目录 一.Collection及子类/接口容器继承关系 二.List 2.1 ArrayList 2.1.1 序列化的探讨 2.1.2 删除元素 2.1.3 调整大小 2.2 Vector和Stack(不建议继续使用) 2.3 抽象类AbstractSequentialList 三.Set 3.1 HashSet和LinkedHashSet 3.2 TreeSet 四.Queue 4.1 PriorityQueue 4.2 LinkedList 五.一些琐碎的话题 5.1 线程安全 5.2 cl

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

初始化顺序的规则 1.在一个类的对象实例化时,成员变量首先初始化,然后才调用构造器,无论书写顺序.如果调用构造器前,没有显式初始化,那么会赋默认值. 这样做法的原因可以理解为:构造器执行时可能会用到一些成员变量的初值. 2.static变量早于所有其他的类成员变量初始化,同样无论书写顺序.但是static变量仅在所在类第一次被使用时初始化一次. 3.基类构造器总是在导出类的构造过程中被调用,而且按照继承层级逐渐向上链接(调用顺序则是从基类开始向下).可以理解为,这么做的逻辑关系是在一个类构建时可

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

在对Java学习的过程中,对于转型这种操作比较迷茫,特总结出了此文.例子参考了<Java编程思想>. 目录 几个同义词 向上转型与向下转型 例一:向上转型,调用指定的父类方法 例二:向上转型,动态绑定 例三:向上转型,静态绑定 例四:向下转型 转型的误区 1.运行信息(RTTI) 2.数组类型 3.Java容器 几个同义词 首先是几组同义词,由于在不同的书上使用的不同,是造成混淆的原因之一. 父类/超类/基类 子类/导出类/继承类/派生类 静态绑定/前期绑定 动态绑定/后期绑定/运行时绑定 向

java入门第二步之helloworld【转】

前一篇博客已经介绍了jdk的安装:接下来我们就乘热打铁,完成第一个程序:helloworld(每学一样程序的新东西都是从实现helloworld开始的) 1.不是用开发工具IDE,只是使用记事本来实现: 打开计算本,在记事本上写如下代码,现在可以先不管是什么意思: public class HelloWorld { public static void main(String[] args) { System.out.println("这是我的第一个程序!!"); } } 保存,文件名

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

开始学习java了,搭好环境,notepad++中新建一个java文件,新建一个HelloWorld类, public class HelloWorld { public static void main(String[] args) { System.out.println("Hello World!"); } } 保存文件时取名firstjava.java,然后进入命令行开始编译,然而编译时却出现一个错误,“类HelloWorld是公共的,应在名为HelloWorld.java的文

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

断更了一个多月,阅读量立马从100+跌落至10-,虽说不是很看重这个,毕竟只是当这个是自己的学习笔记,但有人看,有人评论,有人认同和批评的感觉还是很巴适的,尤其以前有过却又被剥夺的,惨兮兮的. 好好写吧. 现在开展的“业务”,一个是PHP,一个是Android开发. 前者偏向于三个方向,总结之前的(看书,敲代码实现),电商网站的开发,一些技能的实现: 后者起步阶段,一边Java学习,一边动手做东西出来,争取一周内有个交代吧先. 今天还比较坑一点,把昨天弄的卸载了,AS中的虚拟界面用不了,用哥们儿

java入门程序、常量、变量

1. Java语言的发展历史 1.什么是Java语言 Java语言是美国Sun公司(Stanford University Network),在1995年推出的高级的编程语言.所谓编程语言,是 计算机的语言,人们可以使用编程语言对计算机下达命令,让计算机完成人们需要的功能. 2.Java语言发展历史 1995年Sun公司发布Java1.0版本 1997年发布Java 1.1版本 1998年发布Java 1.2版本 2000年发布Java 1.3版本 2002年发布Java 1.4版本 2004年

Java - Java入门(2-1am)

第一讲.Java入门 1. 计算机语言是人和计算机进行交互的一种工具,人们通过使用计算机语言编写程序来向计算机施令,计算机则执行程序,并把结果输出给用户. 2. 机器语言:由0.1序列构成的指令码组成 如:10000000  加        10010000减          11110000 打印 3. 汇编语言:用助记符号描述的指令系统 如: Mov   A, #10; Mov   B, #20; Add   A,  B; 4. 高级语言: 面向过程的高级语言:程序设计的基本单元为函数