Java 相关知识的学习(第一章至第三章)

学习的书籍:《Java核心技术+卷1:基础知识(原书第9版)》

关于本书

第一章:概述 Java 与其他程序设计语言不同的性能;

第二章:详细地论述如何下载和安装 JDK 以及本书的程序示例;

第三章:开始讨论 Java语言;

第四章:将介绍面向对象两个基本成分中最重要的—封装, 以及Java语言实现封装的机制, 即类与方法;

第五章:将介绍另一部分—继承;

第六章:展示如何使用 Java的接口;

第七章:开始细致地讨论应用程序设计;

第八章:详细讨论 AWTAbstract Window Toolkit) 的事件模型;

第九章:详细讨论Swing GUI工具箱;

第十章:阐述如何部署自己编写的应用程序或applet;

十一章:讨论异常处理, 即Java的健壮机制, 它用于处理调试好的程序可能出现意外的情况;

第十二章:概要介绍泛型程序设计, 这是Java SE 5.0的一项重要改进;

第十三章:介绍Java平台的集合框架; 

第十四章:在这一章中将介绍多线程, 这是一种可以让程序任务并行执行的特性(线程是程序中的控制流), 并阐述如何建立线程、 如何处理线程的同步问题;

第 1 章 Java 程序设计概述

1.1 Java 程序设计平台 

Java 与其他程序设计语言不同的性能。

1.2 Java“白皮书” 的关键术语

Java 白皮书的关键术语:简单性、面向对象、网络技能(Network-Savvy)、健壮性、 安全性、 体系结构中立、可移植性、 解释型、 高性能、多线程、 动态性。

1.3 Java applet Internet

在网页中运行 Java 程序称为 applet,为了使用 applet, 需要启用 Java 的 Web 浏览器执行字节码。 
当 applet 首次出现时, 人们欣喜若狂。 许多人相信 applet 的魅力将会导致 Java 迅速地流行起来。 然而, 初期的兴奋很快就淡化了。 不同版本的 Netscape 与 Internet Explorer 运行不同版本的 Java, 其中有些早已过时。 这种糟糕的情况导致更加难于利用 Java 的最新版本开发 applet。 今天, 当需要在浏览器中显示动态效果时, 大多数网页都直接使用 JavaScript 或Flash。 

1.4 Java 发展简史

1996 年年初, Sun 发布了 Java 的第 1 个版本。  坦率地说, Java 1.0 的确没有为其黄金时期的到来做好准备。
后来的 Java1.1 弥补了其中的大多明显的缺陷, 大大改进了反射能力, 并为 GUI 编程增加了新的事件处理模型。 不过它仍然具有很大的局限性。
1998 JavaOne 会议的头号新闻是即将发布 Java 1.2 版。 1998 12 Java 1.2 发布三天之后, Sun 公司市场部将其名称改为更加吸引人的“ Java 2 标准版软件开发工具箱 1.2 版”。
标准版的 1.3 1.4 版本对最初的 Java 2 版本做出了某些改进, 扩展了标准类库, 提高系统性能。 当然, 还修正了一些 bug 。  
5.0 版是自 1.1 版以来第一个对 Java 语言做出重大改进的版本(这一版本原来被命名为 1.5 版, 在 2004 年的 JavaOne 会议之后, 版本数字升至 5.0 )。
版本 6 (没有后缀 .0 ) 于 2006 年年末发布。
直到 2011 Oracle 发布了 Java 的一个新版本, Java 7 , 其中只做了一些简单的改进,而决定将重要的改进推迟到 Java 8 , 该版本将在 2013 年发布。 

1.5 关于 Java的常见误解

列出了一些关于 Java 的常见误解, 同时给出了解释。

2 Java程序设计环境

2.1 安装Java开发工具箱

Oracle 公司为 Linux、 Mac OS X、 Solaris 和 Windows 提供了 Java 开发工具箱(JDK) 的最新、 最完整的版本。

在完成了 JDK 的安装之后, 还需要执行另外一个步骤: 把 jdk/bin 目录添加到执行路径中。 
库源文件在 JDK 中以一个压缩文件 src.zip 的形式发布, 必须将其解压缩后才能够访问源代码。 
文档包含在一个压缩文件中, 它是一个独立于 JDK 的压缩文件。 可以直接从网站 http://www.oracle.com/technetwork/java/javase/downloads 下载获得这个文档。
在学习 Java 的过程中, 经常需要查看 Java 源文件。
就学习 Java 而言, docs 和 src 是两个最有用的子目录。

2.2 选择开发环境

使用 Eclipse 和 NetBeans 这两个免费的开发环境是很好的选择。 
总之, 应当了解如何使用基本的 JDK 工具, 这样才会感觉使用集成开发环境是一种享受。 

2.3 使用命令行工具

首先介绍较难的一种方法: 通过命令行编译并运行 Java 程序。
javac 程序是一个 Java 编译器。 它将文件 Welcom.java
编译成 Welcom.class, 并发送到 Java 虚拟机。 虚拟机执行编译器放在 class 文件中的字节码。

2.4 使用集成开发环境

书里面试用的Eclipse 编译程序; Eclipse 是采用 java 编写的, 然而, 由于所使用的是非标准视窗类库, 所以, 不如 Java 那样具有可移植性。
使用步骤如下所示:
1) 启动 Eclipse 之后, 从菜单选择 File → New → Java Project(如图 2-1 所示)。这些是 Eclipse 4.2 的屏幕画面。如果使用的 Eclipse 版本稍有差异, 不必介意。
2) 键入工程名 Welcome,如图 2-2 所示。点击Next。
3) 点击Link additional source,导入Welcome工程,如图 2-3 所示。
4) 点击 Finish 按钮。 这个工程已经创建完成了。
5) 在工程窗口中 点击Default package旁边的三角, 再双击Welcome.java。 现在应该看到带有程序代码的窗口了
6) 用鼠标右键点击最左侧窗格中的工程名(Welcome), 选择RunRun AsJava Application。 可以看到: 这个窗口底部出现了一个输出窗口。 在这个输出窗口中显示了程序的输出结构(如图2-4所示)。
图2-1

图2-1

图2-3


图2-4

2.5 运行图形化应用程序

这个程序是一个简单的图像文件查看器(viewer), 它可以加载并显示一个图像。
首先, 由命令行编译并运行这个程序。
1) 打开一个 shell 窗口。
2) 进入 CoreJavaBook/v1ch02/ImageViewer。
3) 输入:
javac ImageViewer.java
java ImageViewer
运行后将弹出一个标题栏为 ImageViewer 的新程序窗口(如图 2-5 所示)。现在, 选择 File → Open, 然后找到一个图像文件并打开它(我们在同一个目录下提供了两个示例文件)。 要想关闭这一程序, 只需要点击标题栏中的关闭按钮或者从菜单中选择 File → Exit。
图2-5
2.6 建立并运行 applet
本书给出的前两个程序是 Java 应用程序。 它们与所有本地程序一样, 可以独立地运行。然而, 正如第 1 章提到的, 有关 Java 的大量宣传都在炫耀 Java 能够在浏览器中运行applet的能力。 下面介绍一下如何利用命令行建立并运行 applet。 然后, 利用 JDK 自带的 applet 查看器加载 applet。 最后, 在 Web 浏览器中显示。
首先, 打开  shell  窗口并将目录转到  CoreJavaBook/v1ch02/WelcomeApplet , 然后, 输入下面的命令
javac WelcomeApplet.java
appletviewer WelcomeApplet.html
2-6  显示了在 applet 查看器窗口中显示的内容。 
图2-6
第一条命令是大家已经非常熟悉的调用 Java 编译器的命令。 它将 WelcomeApplet.java 源文件编译成字节码文件 WelcomeApplet.class。不过这一次不要运行 Java 程序, 而调用 appletviewer 程序。这是 JDK 自带的一个特殊工具, 它可以帮助人们快速地测试 applet。 这里需要向这个程序提供一个 HTML 文件名, 而不是 Java 类文件名。在本章中, 我们学习了有关编译和运行 Java 程序的机制。 现在可以转到第 3 章开始学习Java 语言了。 

第 3 章 Java 的基本程序设计结构

现在, 假定已经成功地安装了 JDK, 并且能够运行第 2 章中给出的示例程序。 我们从现在开始将介绍 Java 应用程序设计。 本章主要讲述程序设计相关的基本概念(如数据类型、 分支以及循环) 在 Java 中的实现方式。

3.1 一个简单的 Java 应用程序

下面看一个最简单的 Java 应用程序, 它只发送一条消息到控制台窗口中:
public class FirstSample
{
	public static void main(String[] args)
	{	
	 	System.out.println("We will not use 'Hello World!'");
	}
}
这个程序虽然很简单, 但所有的 Java 应用程序都具有这种结构, 还是值得花一些时间研究一下。
首先, Java 对大小写敏感。如果出现了大小写拼写错误(例如, 将 main 拼写成Main), 那程序将无法运行。
下面逐行地查看一下这段源代码:
  • 关键字 public 称为访问修饰符(access modifier), 它用于控制程序的其他部分对这段代码的访问级别。 
  • 关键字 class 表明 Java 程序中的全部内容都包含在类中。
  • 关键字 class 后面紧跟类名。  名字必须以字母开头, 后面可以跟字母和数字的任意组合。 
  • 从类名 FirstSample 可以看出, 标准的命名规范为: 类名是以大写字母开头的名词。 如果名字由多个单词组成, 每个单词的第一个字母都应该大写。
  • 源代码的文件名必须与公共类的名字相同, 并用 .java 作为扩展名。因此, 存储这段源代码的文件名必须为 FirstSample.java(再次提醒大家注意, 大小写是非常重要的, 千万不能写成 firstsample.java)。
  • 如果已经正确地命名了这个文件, 并且源代码中没有任何录入错误, 在编译这段源代码之后就会得到一个包含这个类字节码的文件。 Java 编译器将字节码文件自动地命名为 FirstSample.class, 并与源文件存储在同一个目录下。 
  • 最后, 使用下面这行命令运行这个程序:java FirstSample
  • 程序执行之后, 控制台上将会显示“We will not use‘Hello,World’ !”。
  • 当使用 java ClassName  运行编译程序时, Java 虚拟机将从指定类中的 main 方法开始执行(这里的“方法” 就是Java 中所说的“函数”), 因此为了代码能够执行, 在类的源文件中必须包含一个 main 方法。当然, 也可以将用户自定义的方法添加到类中, 并且在 main 方法中调用它们。
  • 需要注意源代码中的括号 { }。 在 Java 中, 像在 C/C++ 中一样, 用花括号划分程序的各个部分(通常称为块)。 Java 中任何方法的代码都用“{” 开始, 用“}” 结束。
  • 我们暂且不去理睬关键字 static void, 而仅把它们当作编译 Java 应用程序必要的部分就行了。
  • 现在需要记住: 每个 Java 应用程序都必须有一个 main 方法, 其格式如下所示:
  • public class ClassName
    {
       public static void main(String[] args)
       {
         program statements
       }
    }
  • 接下来, 研究一下这段代码:
  • {	
    	System.out.println("We will not use 'Hello World!'");
    }
  • 一对花括号表示方法体的开始与结束, 在这个方法中只包含一条语句。
  •  在 Java 中, 每个句子必须用分号结束。
  • 在上面这个 main 方法体中只包含了一条语句, 其功能是: 将一个文本行输出到控制台上。
  • 在这里, 使用了 System.out 对象并调用了它的 println 方法。 注意, 点号(· ) 用于调用方法。 
  • Java 使用的通用语法是:    这等价于函数调用。
    object.method(parameters)
  • 在这个示例中, 调用了 println 方法并传递给它一个字符串参数。 这个方法将传递给它的字符串参数显示在控制台上。 然后, 终止这个输出行, 以便每次调用 println 都会在新的一行上显示输出。
  • 与其他程序设计语言一样, 在 Java 的方法中, 可以没有参数, 也可以有一个或多个参数(有的程序员把参数叫做实参)。 对于一个方法, 即使没有参数也需要使用空括号。 例如, 不带参数的 println 方法只打印一个空行。 使用下面的语句:
    System.out.println()

    3.2 注释

与大多数程序设计语言一样, Java 中的注释也不会出现在可执行程序中。 因此, 可以在源程序中根据需要添加任意多的注释, 而不必担心可执行代码会膨胀。 在 Java 中, 有三种书写注释的方式。 最常用的方式是使用 //, 其注释内容从 // 开始到本行结尾。
System.out.println("We will not use 'Hello World'!"); //is this too cute?
当需要长篇的注释时, 既可以在每行的注释前面标记 //, 也可以使用 /* 和 */ 将一段比较长的注释括起来。
第三种注释可以用来自动地生成文档。 这种注释以 /** 开始, 以 */ 结束。 有关这种注释的详细内容和自动生成文档的具体方法请参见第 4 章。
3.3 数据类型
Java 是一种强类型语言。 这就意味着必须为每一个变量声明一种类型。 在 Java 中, 一共有 8 种基本类型(primitive type), 其中有 4 种整型、 2 种浮点类型、 1 种用于表示 Unicode 编码的字符单元的字符类型 char(请参见论述 char 类型的章节) 和 1 种用于表示真值的 boolean类型。
3.3.1 整型
整型用于表示没有小数部分的数值, 它允许是负数。 Java 提供了 4 种整型, 具体内容如图 3-1 所示。

图3-1
在通常情况下, int 类型最常用。 但如果表示星球上的居住人数, 就需要使用 long 类型了。 byte 和 short 类型主要用于特定的应用场合, 例如, 底层的文件处理或者需要控制占用存储空间量的大数组。
长 整 型 数 值 有 一 个 后 缀 L(如 4000000000L)。 十 六 进 制 数 值 有 一 个 前 缀 0x(如0xCAFE)。 八进制有一个前缀 0, 例如, 010 对应八进制中的 8。 很显然, 八进制表示法比较容易混淆, 所以建议最好不要使用八进制常数。
从 Java 7 开始, 加上前缀 0b 就可以写二进制数。 例如, 0b1001 就是 9。 另外, 同样是从Java 7 开始, 还可以为数字字面量加下划线, 如用 1_000_000(或 0b1111_0100_0010_0100_0000)表示一百万。 这些下划线只是为了让人更易读。 Java 编译器会去除这些下划线。
3.3.2 浮点类型
浮点类型用于表示有小数部分的数值。 在 Java 中有两种浮点类型, 具体内容如图 3-2 所示。

图3-2
double 表示这种类型的数值精度是 float 类型的两倍(有人称之为双精度数值)。 绝大部分应用程序都采用 double 类型。 在很多情况下, float 类型的精度很难满足需求。例如, 用 7位有效数字足以精确地表示普通雇员的年薪, 但表示公司总裁的年薪可能就不够用了。 实际上, 只有很少的情况适合使用 float 类型, 例如, 需要快速地处理单精度数据, 或者需要存储大量数据。
float 类型的数值有一个后缀 F(例如, 3.14F)。 没有后缀 F 的浮点数值(如 3.14) 默认为double 类型。 当然, 也可以在浮点数值后面添加后缀 D(例如, 3.14D)。
所有的浮点数值计算都遵循 IEEE 754 规范。 下面是用于表示溢出和出错情况的三个特殊的浮点数值:
  • 正无穷大
  • 负无穷大
  • NaN(不是一个数字)
例如, 一个正整数除以 0 的结果为正无穷大。 计算 0/0 或者负数的平方根结果为 NaN。
注释:常量 Double.POSITIVE_INFINITY、 Double.NEGATIVE_INFINITY 和 Double.NaN(与相应的 Float 类型的常量一样) 分别表示这三个特殊的值, 但在实际应用中很少遇到。
特别要说明的是, 不能这样检测一个特定值是否等于 Double.NaN:
if(x == Double.NaN) //is never true
所有“非数值” 的值都认为是不相同的。 然而, 可以使用 Double.isNaN 方法:
if(Double.isNaN(x)) //check whether x is "not a number"
3.3.3 char 类型
char 类型用于表示单个字符。 通常用来表示字符常量。 例如: 'A' 是编码为 65 所对应的字符常量。 与 "A" 不同, "A" 是一个包含字符 A 的字符串。 Unicode 编码单元可以表示为十六进制值, 其范围从 \u0000 到 \Uffff。 例如: \u2122 表示注册符号(TM), \u03C0 表示希腊字母 p。
除了可以采用转义序列符 \u 表示 Unicode 代码单元的编码之外, 还有一些用于表示特殊字符的转义序列符, 请参看图 3-3。

图3-3
所有这些转义序列符都可以出现在字符常量或字符串的引号内。 例如, '\u2122' 或 "Hello\n"。 转义序列符 \u 还可以出现在字符常量或字符串的引号之外(而其他所有转义序列不可以)。 例如:
 public static void main(String\u005B\u005D args)
这种形式完全符合语法规则, \u005B 和 \u005D 是 [ 和 ] 的编码。
要想弄清 char 类型, 就必须了解 Unicode 编码表。 Unicode 打破了传统字符编码方法的限制。 在 Unicode 出现之前, 已经有许多种不同的标准: 美国的 ASCII、 西欧语言中的 ISO8859-1、 俄国的 KOI-8、 中国的 GB 18030 和 BIG-5 等。 这样就产生了下面两个问题: 一个是对于任意给定的代码值, 在不同的编码方案下有可能对应不同的字母; 二是采用大字符集的语言其编码长度有可能不同。 例如, 有些常用的字符采用单字节编码, 而另一些字符则需要两个或更多个字节。
设计 Unicode 编码的目的就是要解决这些问题。
我们强烈建议不要在程序中使用 char 类型, 除非确实需要对 UTF-16 代码单元进行操作。 最好将需要处理的字符串用抽象数据类型表示(有关这方面的内容将在 3.6 节讨论)。
3.3.4 boolean 类型
boolean (布尔) 类型有两个值: false true , 用来判定逻辑条件。 整型值和布尔值之间不能进行相互转换。

3.4 变量

在 Java 中, 每一个变量属于一种类型(type)。 在声明变量时, 变量所属的类型位于变量名之前。 这里列举一些声明变量的示例:
double salary;
int vacationDays;
long earthPopulation;
boolean done;
可以看到, 每个声明以分号结束。 由于声明是一条完整的语句, 所以必须以分号结束。
变量名必须是一个以字母开头的由字母或数字构成的序列。需要注意, 与大多数程序设计语言相比, Java 中“字母” 和“数字” 的范围要大。 字母包括 'A' ~ 'Z'、 'a' ~ 'z'、 '_'、 '$'或在某种语言中代表字母的任何 Unicode 字符。 例如, 德国的用户可以在变量名中使用字母‘ä’ ; 希腊人可以用 p。 同样, 数字包括 '0' ~ '9' 和在某种语言中代表数字的任何 Unicode 字符。 但 '+' 和 '©' 这样的符号不能出现在变量名中, 空格也不行。 变量名中所有的字符都是有意义的, 并且大小写敏感。 变量名的长度没有限制。
  • 提示: 如果想要知道哪些 Unicode 字符属于 Java 中的“字母”, 可以使用 Character 类的isJavaIdentifierStart 和 isJavaIdentifierPart 方法进行检测。
  • 提示: 尽管 $ 是一个合法的 Java 字符, 但不要在你自己的代码中使用这个字符。 它只用在 Java 编译器或其他工具生成的名字中。
可以在一行中声明多个变量:
int i,j; //both are integers
不过, 不提倡使用这种风格。 逐一声明每一个变量可以提高程序的可读性。

3.4.1 变量初始化

声明一个变量之后, 必须用赋值语句对变量进行显式初始化, 千万不要使用未被初始化的变量。 例如, Java 编译器认为下面语句序列是错误的:
int vacationDays;
System.out.println(vacationDays); // ERROR--variable not initialized
要想对一个已经声明过的变量进行赋值, 就需要将变量名放在等号(=) 左侧, 相应取值的 Java 表达式放在等号的右侧。也可以将变量的声明和初始化放在同一行中。最后, 在 Java 中可以将声明放在代码中的任何地方。 例如, 下列代码的书写形式在 Java中是完全合法的:
double salary = 65000.0;
System.out.println(salary); 
int vacationDays = 12;// OK to declare a variable here
在 Java 中, 变量的声明尽可能地靠近变量第一次使用的地方, 这是一种良好的程序编写风格。

3.4.2 常量

在 Java 中, 利用关键字 final 指示常量。 例如:
public class Constants
{
    public static void main(String[] args)
    {
        final double CM_PER_INCH = 2.54;
        double paperWidth = 8.5;
        double paperHeight = 11;
        System.out.println("Paper size in centimeters:"
                + paperWidth * CM_PER_INCH + paperHeight * CM_PER_INCH);
    }
}
关键字 final 表示这个变量只能被赋值一次。 一旦被赋值之后, 就不能够再更改了。 习惯上,常量名使用全大写。
在 Java 中, 经常希望某个常量可以在一个类中的多个方法中使用, 通常将这些常量称为类常量。 可以使用关键字 static final 设置一个类常量。 下面是使用类常量的示例:
public class Constants2
{
    public static final double CM_PER_INCH = 2.54;

    public static void main(String[] args)
    {
        double paperWidth = 8.5;
        double paperHeight = 11;
        System.out.println("Paper size in centimeters:"
                + paperWidth * CM_PER_INCH + paperHeight * CM_PER_INCH);
    }
}
需要注意, 类常量的定义位于 main 方法的外部。 因此, 在同一个类的其他方法中也可以使用这个常量。 而且, 如果一个常量被声明为 public, 那么其他类的方法也可以使用这个常量。 在这个示例中, Constants2.CM_PER-INCH 就是这样一个常量。

3.5 运算符

在 Java 中, 使用算术运算符+、 -、 *、 / 表示加、 减、 乘、 除运算。 当参与 / 运算的两个操作数都是整数时, 表示整数除法; 否则, 表示浮点除法。 整数的求余操作(有时称为取模) 用 % 表示。 例如, 15/2 等于 7, 15%2 等于 1, 15.0/2 等于 7.5。
需要注意, 整数被 0 除将会产生一个异常, 而浮点数被 0 除将会得到无穷大或 NaN 结果。
可以在赋值语句中采用一种简化的格式书写二元算术运算符。例如,x += 4; 等价于 x = x+4; (通常, 将运算符放在赋值号的左侧, 如 *= 或 %=。)

3.5.1 自增运算符与自减运算符

当然, 程序员都知道加 1、 减 1 是数值变量最常见的操作。 在 Java 中, 借鉴了 C 和 C++的实现方式, 也使用了自增、 自减运算符: n++ 将变量 n 的当前值加 1 ; n–– 将 n 的值减 1。例如:
int n = 12;
n++;
n 的值将变为 13。 因为这些运算符改变了变量的值, 所以它的操作数不能是数值。 例如,4++ 就是一条非法的语句。
实际上, 这两个运算符有两种形式。 上面介绍的是运算符放在操作数后面的“后缀” 形式, 还有一种“前缀” 形式, ++n。 两种方式都是对变量值加 1。 但在表达式中, 这两种形式就有区别了。 前缀方式先进行加 1 运算; 后缀方式则使用变量原来的值。
int m = 7;
int n = 7;
int a = 2 * ++m; // now a is 16,m is 8
int b = 2 * n++; //now b is 14,n is 8
我们建议不要在其他表达式的内部使用 ++, 这样编写的代码很容易令人困惑, 并会产生烦人的 bug。

3.5.2 关系运算符与boolean运算符

Java 包含各种关系运算符。 其中, 使用两个等号 = = 检测是否相等。 例如, 3 = = 7 的值为 false
使用
!= 检测是否不相等。 例如, 3 != 7 的值为 true
另外, 经常使用的运算符还有
< (小于)、 > (大于)、 <= (小于等于) 和 >= (大于等于)。
Java 沿用了 C++ 的习惯, 用 && 表示逻辑“与”、 用 || 表示逻辑“或”。 从 != 运算符很容易看出, ! 表示逻辑“非”。 && 和 || 是按照“短路” 方式求值的。 如果第一个操作数已经能够确定表达式的值, 第二个操作数就不必计算了。 如果用 && 对两个表达式进行计算: expression1 && expression2 并且第一个表达式值为 false, 结果不可能为真。因此, 第二个表达式的值就没有必要计算了。 这种方式可以避免一些错误的发生。例如, 表达式:
x !=0 && 1 / x > x + y // no division by 0
当 x 为 0 时, 不会计算第二部分。 因此, 若 x 为 0, 1/x 不被计算, 也不会出现除以 0 的错误。
与之类似, 对于 expression1 || expression2, 当第一个表达式为 true 时, 结果自动为 true,不必再计算第二部分。
最后, Java 支持三元操作符 ?:。 在很多时候, 这个操作符非常有用。 表达式condition ? expression1 : expression2 当条件 condition 为真时计算第 1 个表达式, 否则计算第 2 个表达式。例如: x < y ? x : y 返回 x 和 y 中较小的那个值。
3.5.3 位运算符
在处理整型数值时, 可以直接对组成整型数值的各个位进行操作。 这意味着可以使用屏蔽技术获得整数中的各个位。 位运算符包括:&(" 与 ")、 |(" 或 ")、 ^(" 异或 ")、 ~(" 非 ")
这些运算符在位模式下工作。 例如, 如果 n 是一个整型变量, 并且用二进制表示的 n 从右数第 4 位为 1, 那么int fourthBitFromRight = (n & 0b1000) / 0b1000; 
返回 1 ; 否则返回 0。 通过运用 2 的幂次方的 & 运算可以将其他位屏蔽掉, 而只保留其中的某一位。
另外,“>>” 和“<<” 运算符将二进制位进行右移或左移操作。 当需要建立位模式屏蔽某些位时, 使用这两个运算符十分方便:int fourthBitFromRight = (n & (a << 3)) >> 3;  最后, >>> 运算符将用 0 填充高位; >> 运算符用符号位填充高位。 没有 <<< 运算符。

3.5.4 数学函数与常量

在 Math 类中, 包含了各种各样的数学函数。 在编写不同类别的程序时, 可能需要的函数也不同。
要想计算一个数值的平方根, 可以使用 sqrt 方法:
 double x = 4;
 double y = Math.sqrt(x);
 System.out.println(y); // ptints 2.0
println 方法和 sqrt 方法存在微小的差异。 println 方法操作一个定义在 System 类中的 System.out 对象。 但是, Math 类中的 sqrt 方法处理的不是对象, 这样的方法被称为静态方法。 有关静态方法的详细内容请参看第 4 章。
在 Java 中, 没有幂运算, 因此需要借助于 Math 类的 pow 方法。 语句: double y = Math.pow(x. a); 将 y 的值设置为 x 的 a 次幂(xa)。 pow 方法有两个 double 类型的参数, 其返回结果也为double 类型。
Math 类提供了一些常用的三角函数: Math.sin、Math.cos、Math.tan、Math.atan、Math.atan2
还有指数函数以及它的反函数—自然对数以及以 10 为底的对数:Math.exp、Math.log、Math.log10
最后, Java 还提供了两个用于表示 p 和 e 常量的近似值:Math.PI、Math.E

3.5.5 数值类型之间的转换

在程序运行时, 经常需要将一种数值类型转换为另一种数值类型。 图 3-4 给出了数值类型之间的合法转换。
图3-4 数值类型之间的合法转换
在图 3-4 中有 6 个实心箭头, 表示无信息丢失的转换; 有 3 个虚箭头, 表示可能有精度损失的转换。 例如, 123 456 789 是一个大整数, 它所包含的位数比 float 类型所能够表达的位数多。 当将这个整型数值转换为 float 类型时, 将会得到同样大小的结果, 但却失去了一定的精度。
int n = 123456789;
float f = n; // f is 1.234567892E8
使用上面两个数值进行二元操作时(例如 n + f, n 是整数, f 是浮点数), 先要将两个操作数转换为同一种类型, 然后再进行计算。
如果两个操作数中有一个是 double 类型, 另一个操作数就会转换为 double 类型。
● 否则, 如果其中一个操作数是 float 类型, 另一个操作数将会转换为 float 类型。
● 否则, 如果其中一个操作数是 long 类型, 另一个操作数将会转换为 long 类型。
● 否则, 两个操作数都将被转换为 int 类型。

3.5.6 强制类型转换

在上一小节中看到, 在必要的时候, int 类型的值将会自动地转换为 double 类型。 但另一方面, 有时也需要将 double 转换成 int。 在 Java 中, 允许进行这种数值之间的类型转换。
当然, 有可能会丢失一些信息。 在这种情况下, 需要通过强制类型转换(cast) 实现这个操作。 强制类型转换的语法格式是在圆括号中给出想要转换的目标类型, 后面紧跟待转换的变量名。 例如:
double x = 9.997;
int nx = (int)x;
现在, 变量 nx 的值为 10。 当调用 round 的时候, 仍然需要使用强制类型转换(int)。 其原因是 round 方法返回的结果为 long 类型, 由于存在信息丢失的可能性, 所以只有使用显式的强制类型转换才能够将 long 类型转换成 int 类型。
  • 警告: 如果试图将一个数值从一种类型强制转换为另一种类型, 而又超出了目标类型的表示范围, 结果就会截断成一个完全不同的值。 例如,(byte) 300 的实际值为 44。

3.5.7 括号与运算符级别

表 3-4 给出了运算符的优先级。 如果不使用圆括号, 就按照给出的运算符优先级次序进行计算。 同一个级别的运算符按照从左到右的次序进行计算(除了表中给出的右结合运算符外。) 例如, 由于 && 的优先级比 || 的优先级高, 所以表达式a && b || c 等价于 (a && b) || c。又因为 += 是右结合运算符, 所以表达式 a += b += c 等价于 a += (b += c),也就是将 b += c 的结果(加上 c 之后的 b) 加到 a 上。

3.5.8 枚举类型

有时候, 变量的取值只在一个有限的集合内。 例如: 销售的服装或比萨饼只有小、 中、大和超大这四种尺寸。 当然, 可以将这些尺寸分别编码为 1、 2、 3、 4 或 S、 M、 L、 X。 但这样存在着一定的隐患。 在变量中很可能保存的是一个错误的值(如 0 或 m)。
针对这种情况, 可以自定义枚举类型。 枚举类型包括有限个命名的值。 例如,
enum Size {SAMLL, MEDILE, LARGE, EXTRA_LARGE};
现在, 可以声明这种类型的变量:
Size s = Size.MEDILE;
Size 类型的变量只能存储这个类型声明中给定的某个枚举值, 或者 null 值, null 表示这个变量没有设置任何值。

3.6 字符串

从概念上讲, Java 字符串就是 Unicode 字符序列。 例如, 串“Java\u2122” 由 5 个 Unicode字符 J、a、v、a 和 TM。Java 没有内置的字符串类型, 而是在标准 Java 类库中提供了一个预定义类,很自然地叫做 String。 每个用双引号括起来的字符串都是 String 类的一个实例:
String e = ""; // an empty string
String greeting = "Hello";

3.6.1 子串

String 类的 substring 方法可以从一个较大的字符串提取出一个子串。 例如:
String geeting = "Hello";
String s = geeting.substring(0, 3);
创建了一个由字符“Hel” 组成的字符串。
substring 方法的第二个参数是不想复制的第一个位置。 这里要复制位置为 0、 1 和 2(从0 到 2, 包括 0 和 2) 的字符。 在 substring 中从 0 开始计数, 直到 3 为止, 但不包含 3。
substring 的工作方式有一个优点: 容易计算子串的长度。 字符串 s.substring(a, b) 的长度为 b–a。 例如, 子串“Hel” 的长度为 3 - 0 = 3。

3.6.2 拼接

与绝大多数的程序设计语言一样, Java 语言允许使用 + 号连接(拼接) 两个字符串。
String expletive = "Expletive";
String PG13 = "deleted";
String message = expletive + PG13;
上述代码将“Expletivedeleted” 赋给变量 message(注意, 单词之间没有空格, + 号按照给定的次序将两个字符串拼接起来)。
当将一个字符串与一个非字符串的值进行拼接时, 后者被转换成字符串(在第 5 章中可以看到, 任何一个 Java 对象都可以转换成字符串)。 例如:
int age = 13;
String rating = "PG" + age;
rating 设置为“PG13”。
这种特性通常用在输出语句中。 例如:
System.out.println("The answer is " + answer);
这是一条合法的语句, 并且将会打印出所希望的结果(因为单词 is 后面加了一个空格, 输出时也会加上这个空格)。

3.6.3 不可变字符串

String 类没有提供用于修改字符串的方法。 如果希望将 greeting 的内容修改为“Help!”,不能直接地将 greeting 的最后两个位置的字符修改为‘p’ 和‘!’ 。 这对于 C 程序员来说,将会感到无从下手。 如何修改这个字符串呢? 在 Java 中实现这项操作非常容易。 首先提取需要的字符, 然后再拼接上替换的字符串:
String geeting = "Hello";
geeting = geeting.substring(0, 3) + "p!";
上面这条语句将 greeting 当前值修改为“Help !”。
由于不能修改 Java 字符串中的字符, 所以在 Java 文档中将 String 类对象称为不可变字符串, 如同数字 3 永远是数字 3 一样, 字符串“Hello” 永远包含字符 H、 e、 l、 l 和 o 的代码单元序列, 而不能修改其中的任何一个字符。  当然, 可以修改字符串变量 greeting, 让它引用另外一个字符串, 这就如同可以将存放 3 的数值变量改成存放 4 一样。

3.6.4 检测字符串是否相等

可以使用 equals 方法检测两个字符串是否相等。 对于表达式:s.equal(t)
如果字符串 s 与字符串 t 相等, 则返回 true ; 否则, 返回 false。 s 与 t 可以是字符串变量, 也可以是字符串常量。 例如, 下列表达式是合法的:
"Hello".equal(greeting)
要想检测两个字符串是否相等, 而不区分大小写, 可以使用 equalsIgnoreCase 方法。
"Hello".equalsIgnoreCase(greeting)
一定不能使用 == 运算符检测两个字符串是否相等! 这个运算符只能够确定两个字符串是否放置在同一个位置上。 当然, 如果字符串放置在同一个位置上, 它们必然相等。 但是,完全有可能将内容相同的多个字符串的拷贝放置在不同的位置上。
String geeting = "Hello"; // initialize greeting to a string
if (geeting == "Hello")
   //probably true
if (geeting.substring(0, 3) == "Hel")
   //probably false
如果虚拟机始终将相同的字符串共享, 就可以使用 == 运算符检测是否相等。 但实际上只有字符串常量是共享的, 而 + 或 substring 等操作产生的结果并不是共享的。 因此, 千万不要使用 == 运算符测试字符串的相等性, 以免在程序中出现糟糕的 bug。 从表面上看, 这种bug 很像随机产生的间歇性错误。

3.6.5 空串与Null串

空串 "" 是长度为 0 的字符串。 可以调用以下代码检查一个字符串是否为空: if (str.length() == 0) 或 if (str.equals(""))
空串是一个 Java 对象, 有自己的串长度(0) 和内容(空)。 不过, String 变量还可以存放一个特殊的值, 名为 null, 这表示目前没有任何对象与该变量关联(关于 null 的更多信息请参见第 4 章)。 要检查一个字符串是否为 null, 要使用以下条件:if (str == null) 或 if (str != null && str.length() != 0) 首先要检查 str 不为 null。 在第 4 章会看到, 如果在一个 null 值上调用方法, 会出现错误。

3.6.6 代码点与代码单元

Java 字符串由 char 序列组成。 从 3.3.3 节“char 类型” 已经看到, char 数据类型是一个采用 UTF-16 编码表示 Unicode 代码点的代码单元。 大多数的常用 Unicode 字符使用一个代码单元就可以表示, 而辅助字符需要一对代码单元表示。
length 方法将返回采用 UTF-16 编码表示的给定字符串所需要的代码单元数量。 例如:
String geeting = "Hello"; 
int n = geeting.length(); // is 5
要想得到实际的长度, 即代码点数量, 可以调用:
int cpCount = geeting.codePointCount(0, geeting.length());
调用 s.charAt(n) 将返回位置 n 的代码单元, n 介于 0 ~ s.length()-1 之间。 例如:
char first = geeting.charAt(0); //first is 'H'
char last = geeting.charAt(4); //first is 'o'
要想得到第 i 个代码点, 应该使用下列语句
int index = geeting.offsetByCodePoints(0, i);
int cp = geeting.codePointAt(index);
如果想要遍历一个字符串, 并且依次查看每一个代码点, 可以使用下列语句:
int cp = sentence.codePointAt(i);
if (Character.isSupplementaryCodePoint(cp)) i += 2;
else i++;
可以使用下列语句实现回退操作:
i--;
if (Character.isSurrogate(sentence.charAt(i))) i--;
int cp = sentence.codePointAt(i);

3.6.7 字符串API

Java 中的 String 类包含了 50 多个方法。 令人惊讶的是绝大多数都很有用, 可以设想使用的频繁非常高。 下面的 API 注释汇总了一部分最常用的方法。
  1. jajava.lang.string va.lang.string 1.0 
    • char charAt (int index) 返回给定位置的代码单元。 
    • int codePointAt(int index) 5.0 返回从给定位置开始或结束的代码点。
    • int offsetByCodePoints(int startIndex, int cpCount) 5.0 返回从 startIndex 代码点开始, 位移 cpCount 后的代码点索引。
    •  int compareTo(String other) 按照字典顺序, 如果字符串位于other之前返回一个负数; 如果字符串位于 other 之后, 返回一个正数; 如果两个字符串相等, 返回 0。
    • boolean endsWith(String suffix) 如果字符串以 suffix 结尾, 返回 true。
    • boolean equals(Object other) 如果字符串与 other 相等, 返回 true。
    •  boolean equalsIgnoreCase(String other) 如果字符串与 other 相等(忽略大小写), 返回 true。
    • ● int index0f(String str)
      ● int index0f(String str, int fromIndex)
      ● int index0f(int cp)
      ● int index0f(int cp, int fromIndex) 返回与字符串 str 或代码点 cp 匹配的第一个子串的开始位置。 这个位置从索引 0 或 fromIndex 开始计算。 如果在原始串中不存在 str, 返回 –1。
    • ● int lastIndex0f(String str)
      ● int lastIndex0f(String str, int fromIndex)
      ● int lastindex0f(int cp)
      ● int lastindex0f(int cp, int fromIndex) 返回与字符串 str 或代码点 cp 匹配的最后一个子串的开始位置。 这个位置从原始串尾端或 fromIndex 开始计算。
    • int length( ) 返回字符串的长度。
    • int codePointCount(int startIndex, int endIndex) 5.0 返回 startIndex 和 endIndex - 1 之间的代码点数量。 没有配成对的代用字符将计入代码点。
    • String replace(CharSequence oldString,CharSequence newString) 返回一个新字符串。 这个字符串用 newString 代替原始字符串中所有的 oldString。 可以用 String 或 StringBuilder 对象作为 CharSequence 参数。
    • boolean startsWith(String prefix) 如果字符串以 preffix 字符串开始, 返回 true。
    • ● String substring(int beginIndex)
      ● String substring(int beginIndex, int endIndex) 返回一个新字符串。 这个字符串包含原始字符串中从 beginIndex 到串尾或 endIndex–1的所有代码单元。
    • String toLowerCase( ) 返回一个新字符串。 这个字符串将原始字符串中的所有大写字母改成了小写字母。
    •  String toUpperCase( ) 返回一个新字符串。 这个字符串将原始字符串中的所有小写字母改成了大写字母。
    • String trim( ) 返回一个新字符串。 这个字符串将删除了原始字符串头部和尾部的空格。

3.6.8 阅读联机 API 文档

正如前面所看到的, String 类包含许多方法。 而且, 在标准库中有几千个类, 方法数量更加惊人。 要想记住所有的类和方法是一件不太不可能的事情。 因此, 学会使用在线 API 文档十分重要, 从中可以查阅到标准类库中的所有类和方法。 API 文档是 JDK 的一部分, 它是HTML 格式的。 

3.6.9 构建字符串

有些时候, 需要由较短的字符串构建字符串, 例如, 按键或来自文件中的单词。 采用字符串连接的方式达到此目的效率比较低。 每次连接字符串, 都会构建一个新的 String 对象,既耗时, 又浪费空间。 使用 StringBuilder 类就可以避免这个问题的发生。
如果需要用许多小段的字符串构建一个字符串, 那么应该按照下列步骤进行。 首先, 构建一个空的字符串构建器:
StringBuilder builder = new StringBuilder();
当每次需要添加一部分内容时, 就调用 append 方法。
builder.append(ch); // appends a single charcter
builder.append(str); // appends a string
在需要构建字符串时就调用 toString 方法, 将可以得到一个 String 对象, 其中包含了构建器中的字符序列。
String completedString = builder.toString();
下面的 API 注释包含了 StringBuilder 类中的重要方法。
  1. java.lang.StringBuilder 5.0
    • StringBuilder() 构造一个空的字符串构建器。
    • int length() 返回构建器或缓冲器中的代码单元数量。
    • StringBuilder append(String str) 追加一个字符串并返回 this。
    • StringBuilder append(char c) 追加一个代码单元并返回 this。
    • StringBuilder appendCodePoint(int cp) 追加一个代码点, 并将其转换为一个或两个代码单元并返回 this。
    • void setCharAt(int i,char c) 将第 i 个代码单元设置为 c。
    • StringBuilder insert(int offset,String str) 在 offset 位置插入一个字符串并返回 this。
    • StringBuilder insert(int offset,Char c) 在 offset 位置插入一个代码单元并返回 this。
    • StringBuilder delete(int startIndex,int endIndex) 删除偏移量从 startIndex 到- endIndex - 1 的代码单元并返回 this。
    • String toString() 返回一个与构建器或缓冲器内容相同的字符串。

3.7 输入输出

为了增加后面示例程序的趣味性, 需要程序能够接收输入, 并以适当的格式输出。 当然, 现代的程序都使用 GUI 收集用户的输入, 然而, 编写这种界面的程序需要使用较多的工具与技术, 目前还不具备这些条件。 主要原因是需要熟悉 Java 程序设计语言, 因此只要有简单的用于输入输出的控制台就可以了。 第 7 章~第 9 章将详细地介绍 GUI 程序设计。

3.7.1 读取输入

前面已经看到, 打印输出到“标准输出流”(即控制台窗口) 是一件非常容易的事情, 只要调用 System.out.println 即可。 然而, 读取“标准输入流” System.in 就没有那么简单了。 要想通过控制台进行输入, 首先需要构造一个 Scanner 对象, 并与“标准输入流” System.in 关联。
Scanner in = new Scanner(System.in);
(构造器和 new 操作符将在第 4 章中详细地介绍。)
现在, 就可以使用 Scanner 类的各种方法实现输入操作了。 例如, nextLine 方法将输入一行。
System.out.print("What is your name? ");
String name = in.nextLine();
在这里, 使用 nextLine 方法是因为在输入行中有可能包含空格。 要想读取一个单词(以空白符作为分隔符), 就调用
String firstName = in.next();
要想读取一个整数, 就调用 nextInt 方法。
System.out.print("How old are your? ");
int age = in.nextInt();
与此类似, 要想读取下一个浮点数, 就调用 nextDouble 方法。
在程序清单 3-2 的程序中, 询问用户姓名和年龄, 然后打印一条如下格式的消息:
Hello,Cay. Next year,you'll be 52
最后, 在程序的最开始添加上一行:
import java.util.*;
程序清单 3-2 InputTest/InputTest.java
import java.util.*;

/**
 * This program demonstrates console input.
 * @version 1.10 2004-02-10
 * @author Cay Horstmann
 */
public class InputTest
{
   public static void main(String[] args)
   {
      Scanner in = new Scanner(System.in);

      // get first input
      System.out.print("What is your name? ");
      String name = in.nextLine();

      // get second input
      System.out.print("How old are you? ");
      int age = in.nextInt();

      // display output on console
      System.out.println("Hello, " + name + ". Next year, you'll be " + (age + 1));
   }
}
Scanner 类定义在 java.util 包中。 当使用的类不是定义在基本 java.lang 包中时, 一定要使用import 指示字将相应的包加载进来。 有关包与 import 指示字的详细描述请参看第 4 章。
注释: 因为输入是可见的, 所以 Scanner 类不适用于从控制台读取密码。 Java SE 6 特别引入了 Console 类实现这个目的。 要想读取一个密码, 可以采用下列代码:
Console cons = System.console();
String username = cons.readLine("User name: ");
char[] passwd = cons.readPassword("Password: ");
为了安全起见, 返回的密码存放在一维字符数组中, 而不是字符串中。 在对密码进行处理之后, 应该马上用一个填充值覆盖数组元素(数组处理将在本章稍后介绍)。
采用 Console 对象处理输入不如采用 Scanner 方便。 每次只能读取一行输入, 而没有能够读取一个单词或一个数值的方法。
  1. java.util.Scanner 5.0
    • Scanner (InputStream in) 用给定的输入流创建一个 Scanner 对象。
    • String nextLine( ) 读取输入的下一行内容。
    • String next( ) 读取输入的下一个单词(以空格作为分隔符)。
    • int nextInt( ) 
      ● doube nextDouble( ) 读取并转换下一个表示整数或浮点数的字符序列。
    • boolean hasNext( ) 检测输入中是否还有其他单词。
    • ● boolean hasNextInt( ) 
      ● boolean hasNextDouble( ) 检测是否还有表示整数或浮点数的下一个字符序列。
  2. java.lang.System 1.0
    • static Console console( ) 6 如果有可能进行交互操作, 就通过控制台窗口为交互的用户返回一个 Console 对象,否则返回 null。 对于任何一个通过控制台窗口启动的程序, 都可使用 Console 对象。 否则, 其可用性将与所使用的系统有关。
  3. java.io.Console 6
    • ● static char[] readPassword(String prompt, Object...args)
      ● static String readLine(String prompt, Object...args) 显示字符串 prompt 并且读取用户输入, 直到输入行结束。 args 参数可以用来提供输入格式。 有关这部分内容将在下一节中介绍。

3.7.2 格式化输出

可以使用 System.out.print(x) 将数值 x 输出到控制台上。 这条命令将以 x 对应的数据类型所允许的最大非 0 数字位数打印输出 x。 例如:
double x = 1000.0/3.0;
System.out.print(x);
打印 3333.3333333333335
如果希望显示美元、 美分等符号, 则有可能会出现问题。
在早期的 Java 版本中, 格式化数值曾引起过一些争议。 庆幸的是, Java SE 5.0 沿用了 C语言库函数中的 printf 方法。 例如, 调用
System.out.printf("%8.2f", x);
可以用 8 个字符的宽度和小数点后两个字符的精度打印 x。 也就是说, 打印输出一个空格和7 个字符, 如下所示:3333.33
在 printf 中另外, 还可以使用多个参数, 例如:
System.out.printf("Hello, %s, Next year,you'll be %d", name, age);
每一个以 % 字符开始的格式说明符都用相应的参数替换。 格式说明符尾部的转换符将指示被格式化的数值类型: f 表示浮点数, s 表示字符串, d 表示十进制整数。 表 3-5 列出了所有转换符。

另外, 还可以给出控制格式化输出的各种标志。 表 3-6 列出了所有的标志。 例如, 逗号标志增加了分组的分隔符。 即
System.out.printf("%,.2f", 1000.0/3.0);
打印 3,333.33
可以使用多个标志, 例如,“%, ( .2f” 使用分组的分隔符并将负数括在括号内。

可以使用静态的 String.format 方法创建一个格式化的字符串, 而不打印输出:
String message = String.format("Hello, %s, Next year,you'll be %d", name, age);
尽管在第 4 章之前, 没有对 Date 类型进行过详细地描述, 但基于完整性的考虑, 还是简略地介绍一下 printf 方法中日期与时间的格式化选项。 在这里, 使用以 t 开始, 以表 3-7 中任意字母结束的两个字母格式。 例如,
System.out.printf("%tc", new Date());
这条语句将用下面的格式打印当前的日期和时间: Mon Feb 09 18:05:19 PSI 2004

从表 3-7 可以看到, 某些格式只给出了指定日期的部分信息。 例如, 只有日期或月份。 如果需要多次对日期操作才能实现对每一部分进行格式化的目的就太笨拙了。 为此, 可以采用一个格式化的字符串指出要被格式化的参数索引。 索引必须紧跟在 % 后面, 并以 $ 终止。 例如,
System.out.printf("%1$s %2$tB %2$te, %2$tY", "Due date:", new Date());
打印: Due Date:February 9, 2004 
还可以选择使用 < 标志。 它指示前面格式说明中的参数将被再次使用。 也就是说, 下列语句将产生与前面语句同样的输出结果:
System.out.printf("%s %tB %<te, %<tY", "Due date:", new Date());
提示: 参数索引值从 1 开始, 而不是从 0 开始, %1$... 对第 1 个参数格式化。 这就避免了与 0 标志混淆。
现在, 已经了解了 printf 方法的所有特性。 图 3-9  给出了格式说明符的语法图。
图 3-9 格式说明符语法

3.7.3 文件输入与输出

要想对文件进行读取, 就需要一个用 File 对象构造一个 Scanner 对象, 如下所示:
Scanner in = new Scanner(Paths.get("myfile.txt"));
如果文件名中包含反斜杠符号, 就要记住在每个反斜杠之前再加一个额外的反斜杠:“c:\\mydirectory\\myfile.txt”。
现在, 就可以利用前面介绍的任何一个 Scanner 方法对文件进行读取。要想写入文件, 就需要构造一个 PrintWriter 对象。 在构造器中, 只需要提供文件名:
PrintWriter out = new PrintWriter("myfile.txt");
如果文件不存在, 创建该文件。 可以像输出到 System.out 一样使用 print、 println 以及 printf 命令。
正如读者所看到的, 访问文件与使用 System.in 和 System.out 一样容易。 要记住一点: 如果用一个不存在的文件构造一个 Scanner, 或者用一个不能被创建的文件名构造一个PrintWriter, 那么就会发生异常。 Java 编译器认为这些异常比“被零整除” 异常更严重。 在第 11 章中, 将会学习各种处理异常的方式。 现在, 应该告知编译器: 已经知道有可能出现“找不到文件” 的异常。 这需要在 main 方法中用 throws 子句标记, 如下所示:
public static void main(String[] args) throw FileNotFoundExpception
{
	Scanner in = new Sanner(Paths.get("myfile.txt"));
	...
}
现在读者已经学习了如何读写包含文本数据的文件。 对于更加高级的技术, 例如, 处理不同的字符编码、 处理二进制数据、 读取目录以及编写压缩文件, 请参看卷 II 第 1 章。
  1. java.util.Scanner 5.0
    • Scanner(File f) 构造一个从给定文件读取数据的 Scanner。
    • Scanner(String data) 构造一个从给定字符串读取数据的 Scanner。
  2. java.io.PrintWriter 1.1
    • PrintWriter(String fileName) 构造一个将数据写入文件的 PrintWriter。 文件名由参数指定。
  3. java.nio.file.Paths 7 
    • static Path get(String pathname) 根据给定的路径名构造一个 Path。

3.8 控制流程

与任何程序设计语言一样, Java 使用条件语句和循环结构确定控制流程。 本节先讨论条件语句, 然后讨论循环语句, 最后介绍看似有些笨重的 switch 语句, 当需要对某个表达式的多个值进行检测时, 可以使用 switch 语句。

3.8.1 块作用域

在深入学习控制结构之前, 需要了解块(block) 的概念。
块(即复合语句) 是指由一对花括号括起来的若干条简单的 Java 语句。 块确定了变量的作用域。 一个块可以嵌套在另一个块中。 下面就是在 main 方法块中嵌套另一个语句块的示例。
public static void main(String[] args)
{
	int n;
	...
	{
		int k;
		...
	}// k is only defined up to here
}
但是, 不能在嵌套的两个块中声明同名的变量。 例如, 下面的代码就有错误, 而无法通过编译:
public static void main(String[] args)
{
	int n;
	...
	{
		int k;
		ink n;// ERROR --can't redefine n in inner block
		...
	}
}

3.8.2 条件语句

在 Java 中, 条件语句的格式为 if (condition) statement 这里的条件必须用括号括起来。
与绝大多数程序设计语言一样, Java 常常希望在某个条件为真时执行多条语句。 在这种情况下, 应该使用块语句(block statement), 格式为
{
	statement1
	statement2
	...
}
例如:
if (yourSales >= target)
{
	performance = "Satisfactory";
	bonus = 100;
}
当 yourSales 大于或等于 target 时, 将执行括号中的所有语句。
图 3-10 if 语句的流程图
注释: 使用块(有时称为复合语句) 可以在 Java 程序结构中原本只能放置一条简单语句的地方放置多条语句。
在 Java 中, 比较常见的条件语句格式如下所示(请参看图 3-11): if (condition) statement1 else statement2 
图 3-11 if/else 语句的流程图
例如:
if (yourSales >= target)
{
	performance = "Satisfactory";
	bonus = 100 + 0.01 * (yourSales - target);
}
else
{
	performance = "Unsatisfactory";
	bonus = 0;
}
其中 else 部分是可选的。 else 子句与最邻近的 if 构成一组。 因此, 在语句
if (x <= 0) if (x == 0) sign = 0; else sign = -1;
中 else 与第 2 个 if 配对。 当然, 用一对括号将会使这段代码更加清晰:
if (x <= 0) { if (x == 0) sign = 0; else sign = -1 };
重复地交替出现 if...else if... 是一种很常见的情况(请参看图 3-12)。 例如:
图 3-12 if/else if(多分支) 的流程图
if (yourSales >= 2 * target)
{
	performance = "Excellent";
	bonus = 1000;
}
else if (yourSales >= 1.5 * target)
{
	performance = "Fine";
	bonus = 500;
}
else if (yourSales >= target)
{
	performance = "Satisfactory";
	bonus = 100;
}
else
{
	System.out.println("You're fired");
}

3.8.3 循环 

当条件为 true 时, while 循环执行一条语句(也可以是一个语句块)。 常用的格式为 while (condition) statement 
如果开始循环条件的值就为 false, 则 while 循环体一次也不执行(请参看图 3-13 )。

图 3-13 while 语句的流程图

程序清单 3-3 中的程序将计算需要多长时间才能够存储一定数量的退休金, 假定每年存入相同数量的金额, 而且利率是固定的。
在这个示例中, 增加了一个计数器, 并在循环体中更新当前的累积数量, 直到总值超过目标值为止。
  • 程序清单 3-3 Retirement/Retirement.java
import java.util.*;

/**
 * This program demonstrates a <code>while</code> loop.
 * @version 1.20 2004-02-10
 * @author Cay Horstmann
 */
public class Retirement
{
   public static void main(String[] args)
   {
      // read inputs
      Scanner in = new Scanner(System.in);

      System.out.print("How much money do you need to retire? ");
      double goal = in.nextDouble();

      System.out.print("How much money will you contribute every year? ");
      double payment = in.nextDouble();

      System.out.print("Interest rate in %: ");
      double interestRate = in.nextDouble();

      double balance = 0;
      int years = 0;

      // update account balance while goal isn't reached
      while (balance < goal)
      {
         // add this year's payment and interest
         balance += payment;
         double interest = balance * interestRate / 100;
         balance += interest;
         years++;
      }

      System.out.println("You can retire in " + years + " years.");
   }
}
while 循环语句首先检测循环条件。 因此, 循环体中的代码有可能不被执行。 如果希望循环体至少执行一次, 则应该将检测条件放在最后。 使用 do/while 循环语句可以实现这种操作方式。 它的语法格式为: do statement while (condition) ;
这种循环语句先执行语句(通常是一个语句块), 再检测循环条件; 然后重复语句, 再检测循环条件, 以此类推。 在程序清单 3-4 中, 首先计算退休账户中的余额, 然后再询问是否打算退休:
  • 程序清单 3-4 Retirement2/Retirement2.java
import java.util.*;

/**
 * This program demonstrates a <code>do/while</code> loop.
 * @version 1.20 2004-02-10
 * @author Cay Horstmann
 */
public class Retirement2
{
   public static void main(String[] args)
   {
      Scanner in = new Scanner(System.in);

      System.out.print("How much money will you contribute every year? ");
      double payment = in.nextDouble();

      System.out.print("Interest rate in %: ");
      double interestRate = in.nextDouble();

      double balance = 0;
      int year = 0;

      String input;

      // update account balance while user isn't ready to retire
      do
      {
         // add this year's payment and interest
         balance += payment;
         double interest = balance * interestRate / 100;
         balance += interest;

         year++;

         // print current balance
         System.out.printf("After year %d, your balance is %,.2f%n", year, balance);

         // ask if ready to retire and get input
         System.out.print("Ready to retire? (Y/N) ");
         input = in.next();
      }
      while (input.equals("N"));
   }
}
只要用户回答“N”, 循环就重复执行(见图 3-14)。 这是一个需要至少执行一次的循环的很好示例, 因为用户必须先看到余额才能知道是否满足退休所用。
图 3-14 do/while 语句的流程图

3.8.4 确定循环

for 循环语句是支持迭代的一种通用结构, 利用每次迭代之后更新的计数器或类似的变量来控制迭代次数。 如图 3-15 所示, 下面的程序将数字 1 ~ 10 输出到屏幕上。
for (int i = 1;i <= 10;i++)
	System.out.println(i);
for 语句的第 1 部分通常用于对计数器初始化; 第 2 部分给出每次新一轮循环执行前要检测的循环条件; 第 3 部分指示如何更新计数器。

图 3-15 for 语句的流程图
与 C++ 一样, 尽管 Java 允许在 for 循环的各个部分放置任何表达式, 但有一条不成文的规则: for 语句的 3 个部分应该对同一个计数器变量进行初始化、 检测和更新。 若不遵守这一规则, 编写的循环常常晦涩难懂。
即使遵守了这条规则, 也还有可能出现很多问题。 例如, 下面这个倒计数的循环:
for (int i = 1;i > 0;i--)
	System.out.println("Counting down ..." + i);
System.out.println("Blastoff");
当在 for 语句的第 1 部分中声明了一个变量之后, 这个变量的作用域就为 for 循环的整个循环体。
for (int i = 1;i <= 10;i++)
{
	...
}
// i no longer defined here
特别指出, 如果在 for 语句内部定义一个变量, 这个变量就不能在循环体之外使用。 因此, 如果希望在 for 循环体之外使用循环计数器的最终值, 就要确保这个变量在循环语句的前面且在外部声明!
int i;
for (i = 1;i <= 10;i++)
{
	...
}
// i is still defined here
另一方面, 可以在各自独立的不同 for 循环中定义同名的变量:
for (int i = 1;i <= 10;i++)
{
	...
}
...
for (int i = 11;i <= 20;i++)//OK to define anther variable name i
{
	...
}
for 循环语句只不过是 while 循环的一种简化形式。 例如,
for (int i = 10;i > 0;i--)
	System.out.println("Counting down ..." + i);
可以重写为:
int i = 10;
while(i > 0)
{
	System.out.println("Counting down ..." + i);
	i--;
}
程序清单 3-5 给出了一个应用 for 循环的典型示例。 这个程序用来计算抽奖中奖的概率。 例如, 如果必须从 1 ~ 50 之间的数字中取 6 个数字来抽奖, 那么会有 (50×49×48×47×46×45)/(1×2×3×4×5×6) 种可能的结果, 所以中奖的几率是 1/15 890 700。 祝你好运!
一般情况下, 如果从 n 个数字中抽取 k 个数字, 就可以使用下列公式得到结果。

下面的 for 循环语句计算了上面这个公式的值:

int lotteryOdds = 1;
for(int i = 1;i <= k; i++)
	lotteryOdds = lotteryOdds * (n - i + 1) / i;

  • 程序清单 3-5 LotteryOdds/LotteryOdds.java

import java.util.*;

/**
 * This program demonstrates a <code>for</code> loop.
 * @version 1.20 2004-02-10
 * @author Cay Horstmann
 */
public class LotteryOdds
{
   public static void main(String[] args)
   {
      Scanner in = new Scanner(System.in);

      System.out.print("How many numbers do you need to draw? ");
      int k = in.nextInt();

      System.out.print("What is the highest number you can draw? ");
      int n = in.nextInt();

      /*
       * compute binomial coefficient n*(n-1)*(n-2)*...*(n-k+1)/(1*2*3*...*k)
       */

      int lotteryOdds = 1;
      for (int i = 1; i <= k; i++)
         lotteryOdds = lotteryOdds * (n - i + 1) / i;

      System.out.println("Your odds are 1 in " + lotteryOdds + ". Good luck!");
   }
}

3.8.5 多重选择: switch 语句

在处理多个选项时, 使用 if/else 结构显得有些笨拙。 Java 有一个与 C/C++ 完全一样的switch 语句。
例如, 如果建立一个如图 3-17 所示的包含 4 个选项的菜单系统, 就应该使用下列代码:
Scanner in = new Scanner(System.in);
System.out.print("Select an option(1,2,3,4)");
int choice = in.nextInt();
Swich (choice)
        {
        case 1:
            ...
            break;
        case 2:
            ...
            break;
        case 3:
            ...
            break;
        case 4:
            ...
            break;
        default:
            //bad input
            ...
            break;
        }

图 3-17 switch 语句的流程图
switch 语句将从与选项值相匹配的 case 标签处开始执行直到遇到 break 语句, 或者执行到 switch 语句的结束处为止。 如果没有相匹配的 case 标签, 而有 default 子句, 就执行这个子句。
case 标签可以是:
● 类型为 char、 byte、 short 或 int(或其包装器类 Character、 Byte、 Short 和 Intege, 这些包装器类将在第 4 章介绍) 的常量表达式。
● 枚举常量。
● 从 Java SE 7 开始, case 标签还可以是字符串字面量。
例如:
String input = ...;
Swich (input.toLowerCase())
        {
        case "yes": //OK since Java SE 7
            ...
            break;
        ...
        }
当在 switch 语句中使用枚举常量时, 不必在每个标签中指明枚举名, 可以由 switch 的表达式值确定。 例如:
Size sz = ...;
Swich (sz)
        {
        case SMALL: //no need to use Size.SMALL
            ...
            break;
        ...
        }

3.8.6 中断控制流程语句

尽管 Java 的设计者将 goto 作为保留字, 但实际上并没有打算在语言中使用它。 通常,使用 goto 语句被认为是一种拙劣的程序设计风格。 当然, 也有一些程序员认为反对 goto 的呼声似乎有些过分(例如, Donald Knuth 就曾编著过一篇名为《Structured Programming with goto statements》 的著名文章)。 这篇文章说: 无限制地使用 goto 语句确实是导致错误的根源,但在有些情况下, 偶尔使用 goto 跳出循环还是有益处的。 Java 设计者同意这种看法, 甚至在Java 语言中增加了一条带标签的 break, 以此来支持这种程序设计风格。
下面首先看一下不带标签的 break 语句。 与用于退出 switch 语句的 break 语句一样, 它也可以用于退出循环语句。 例如,
while (years <= 100)
{
	balance += payment;
	double interest = balance * interestRate / 100;
	balance += interest;
	if (balance >= goal) break;
	years++;
}
在循环开始时, 如果 years > 100, 或者在循环体中 balance ≥ goal, 则退出循环语句。 当然, 也可以在不使用 break 的情况下计算 years 的值, 如下所示:
while (years <= 100 && balance < goal)
{
	balance += payment;
	double interest = balance * interestRate / 100;
	balance += interest;
	if (balance > goal)
		years++;
}
但是需要注意, 在这个版本中, 检测了两次 balance < goal。 为了避免重复检测, 有些程序员更加偏爱使用 break 语句。
与 C++ 不同, Java 还提供了一种带标签的 break 语句, 用于跳出多重嵌套的循环语句。有时候, 在嵌套很深的循环语句中会发生一些不可预料的事情。 此时可能更加希望跳到嵌套的所有循环语句之外。 通过添加一些额外的条件判断实现各层循环的检测很不方便。
这里有一个示例说明了 break 语句的工作状态。 请注意, 标签必须放在希望跳出的最外层循环之前, 并且必须紧跟一个冒号。
Scanner in = new Scanner(System.in);
int n;
read_data:
while (...) //this loop statement is tagged with the label
{
	...
	for(...) // this inner loop is not labeled
	{
		System.out.print("Enter a number >= 0: ");
		n = in.nextInt();
		if (n < 0) // should never happen-can't go on
			break read_data;
			//break out of read_data loop
			...
	}
}
// this statement is executed immediately after labeled break
if (n < 0) // check for bad situation
{
	//deal with bad situation
}
else 
{
	// carry out normal processing
}
如果输入有误, 通过执行带标签的 break 跳转到带标签的语句块末尾。 对于任何使用break 语句的代码都需要检测循环是正常结束, 还是由 break 跳出。
最后, 还有一个 continue 语句。 与 break 语句一样, 它将中断正常的控制流程。 continue 语句将控制转移到最内层循环的首部。 例如:
Scanner in = new Scanner(System.in);
while(sum < goal)
{
	System.out.print("Enter a number: ");
	n = in.nextInt();
	if (n < 0) continue;
	sum += n; // not exectued if n < 0
}
如果 n<0, 则 continue 语句越过了当前循环体的剩余部分, 立刻跳到循环首部。
如果将 continue 语句用于 for 循环中, 就可以跳到 for 循环的“更新” 部分。 例如, 下面这个循环:
for (count = 1;count <= 100; count++)
{
	System.out.print("Enter a number, -1 to quit:");
	n = in.nextInt();
	if (n < 0) continue;
	sum += n; // not exectued if n < 0
}
如果 n<0, 则 continue 语句跳到 count++ 语句。
还有一种带标签的 continue 语句, 将跳到与标签匹配的循环首部。

3.9 大数值

如果基本的整数和浮点数精度不能够满足需求, 那么可以使用 java.math 包中的两个很有用的类: BigInteger 和 BigDecimal。 这两个类可以处理包含任意长度数字序列的数值。BigInteger 类实现了任意精度的整数运算, BigDecimal 实现了任意精度的浮点数运算。
使用静态的ValueOf 方法可以将普通的数值转换为大数值:
BigInteger a = BigInteger.valueOf(100);
遗憾的是,不能使用人们熟悉的算术运算符(如:+和*)处理大数值。而需要使用大数值类中的add 和 multiply 方法。
BigInteger c = a.add(b); // c = a + b
BigInteger d = c.multiply(b.add(BigInteger.valueOf(2))); // d = c * (b + 2)
程序清单 3-6 是对程序清单 3-5 中彩概率程序的改进, 使其可以采用大数值进行运算。 假设你被邀请参加抽奖活动, 并从 490 个可能的数值中抽取 60 个, 这个程序将会得到中彩概率 1/716395843461995557415116222540092933411717612789263493493351013459481104668848。 祝你好运!
在程序清单 3-5 中,用于计算的语句是 
lotteryOdds = lotteryOdds * (n - i + 1) / i;
如果使用大数值,则相应的语句为:
lotteryOdds = lotteryOdds.multiply(BigInteger.valueOf(n - i + 1)).divide(BigInteger.valueOf(i));
  • 程序清单 3-6 BigIntegerTest/BigIntegerTest.java
    import java.math.*;
    import java.util.*;
    
    /**
     * This program uses big numbers to compute the odds of winning the grand prize in a lottery.
     * @version 1.20 2004-02-10
     * @author Cay Horstmann
     */
    public class BigIntegerTest
    {
       public static void main(String[] args)
       {
          Scanner in = new Scanner(System.in);
    
          System.out.print("How many numbers do you need to draw? ");
          int k = in.nextInt();
    
          System.out.print("What is the highest number you can draw? ");
          int n = in.nextInt();
    
          /*
           * compute binomial coefficient n*(n-1)*(n-2)*...*(n-k+1)/(1*2*3*...*k)
           */
    
          BigInteger lotteryOdds = BigInteger.valueOf(1);
    
          for (int i = 1; i <= k; i++)
             lotteryOdds = lotteryOdds.multiply(BigInteger.valueOf(n - i + 1)).divide(
                   BigInteger.valueOf(i));
    
          System.out.println("Your odds are 1 in " + lotteryOdds + ". Good luck!");
       }
    }
  • API java.math.BigInteger 1.1
    • BigInteger add(BigInteger other)
    • BigInteger subtract(BigInteger other)
    • BigInteger multiply(BigInteger other)
    • BigInteger divide(BigInteger other)
    • BigInteger mod(BigInteger other) 返回这个大整数和另一个大整数 other 的和、 差、 积、 商以及余数。
    •  int compareTo(BigInteger other) 如果这个大整数与另一个大整数other 相等,返回 0;如果这个大整数小于另一个大整数other,返回负数;否则,返回正数。
    • static BigInteger valueOf(long x) 返回值等于 x 的大整数。
    • BigDecimal add(BigDecimal other)
    • BigDecimal subtract(BigDecimal other)
    • BigDecimal multiply(BigDecimal other)
    • BigDecimal divide(BigDecimal other RoundingMode mode) 5.0 返回这个实数与另一个大实数other 的和、差、积、商。要想计算商,必须给出舍入方式(rounding mode)。RoundingMode.HALF_UP 是在学校中学习的四舍五入方式(即,数值 0 到 4 舍去, 数值 5 到 9 进位)。 它适用于常规的计算。 有关其他的舍入方式请参看 API 文档。
    • int compareTo(BigDecimal other) 如果这个大实数与另一个大实数相等,返回0;如果这个大实数小于另一个大实数,返回负数;否则,返回正数。
    • static BigDecimal valueOf(long x)
    • static BigDecimal valueOf(long x,int scale) 返回值为x 或x / 10 scale次方 的一个大实数。

3.10 数组

数组是一种数据结构,用来存储同一类型值的集合。通过一个整型下标可以访问数组中的每一个值。例如,如果a 是一个整型数组,a[i] 就是数组中下标为i 的整数。
在声明数组变量时,需要指出数组类型(数据元素类型紧跟[])和数组变量的名字。下面声明了整型数组a: 
int[] a;
这一条只声明了变量a,并没有将a 初始化为一个真正的数组。应该使用 new 运算符创建数组。
int[] a = new int[100];
这条语句创建了一个可以存储100个整数的数组。数组长度不要求是常量:new int[n] 会创建一个长度为n的数组。
这个数组的下标从0~99(不是1~100)。一旦创建了数组,就可以给数组元素赋值。例如,使用一个循环:
int[] a = new int[100];
for (int i = 0; i < 100; i++)
	a[i] = i; //fills the array with numbers 0 to 99

创建一个数字数组时, 所有元素都初始化为 0。 boolean 数组的元素会初始化为 false。 对象数组的元素则初始化为一个特殊值 null, 这表示这些元素(还) 未存放任何对象。 初学者对此可能有些不解。 例如,
String[] names = new String[10];
会创建一个包含 10 个字符串的数组,所有字符串都为null。如果希望这个数组包含空串,可以为元素指定空串:
for (int i = 0; i < 100; i++) names[i] = "";
要想获得数组中的元素个数,可以使用array.length。例如:
for (int i = 0; i< a.length; i++)
	System.out.println(a[i]);
一旦创建了数组,就不能再改变它的大小(尽管可以改变每一个数组元素)。如果经常需要在运行过程中扩展数组的大小,就应该使用另一种数据结构 -- 数组列表(array list)。

3.10.1 for each 循环

Java 有一种功能很强的循环结构,可以用来依次处理数组中的每个元素(其他类型的元素集合亦可)而不必为制定下标值而分心。
这种增强的 for 循环的语句格式为:
for (variable : collection) statement
定义一个标量用于暂存集合中的每一个元素,并执行相应的语句(当然,也可以是语句块)。collection 这一集合表达式必须是一个数组或者是一个实现了 Iterable 接口的类对象(例如 ArrayList)。
例如:
for (int element : a)
	System.out.println(element);
打印数组a 的每一个元素,一个元素占一行。
这个循环应该读作“循环a 中的每一个元素”(for each element in a)。Java 语言的设计者认为应该使用诸如 foreach、in 这样的关键字,但这种循环语句并不是最初包含在Java 语言中的,而是后来添加进去的,并且没有人打算废除已经包含同名(System.in)方法或变量的旧代码。
当然,使用传统的 for 循环也可以获得同样的效果:
for (int i = 0; i < a.length; i++)
	System.out.println(a[i]);
但是,for each 循环语句显得更加简洁、更不易出错(不必为下标的起始值和终止值而操心)。
如果需要处理一个集合中的所有元素, for each 循环语句对传统循环语句所进行的改进更是叫人称赞不已。 然而, 在很多场合下, 还是需要使用传统的 for 循环。 例如, 如果不希望遍历集合中的每个元素, 或者在循环内部需要使用下标值等。

3.10.2 数组初始化以及匿名数组

在Java 中,提供了一种创建数组对象并同时赋予初始值的简化书写形式。下面是一个例子:
int[] smallPrimes = {2,3,5,7,11,13};
请注意,在使用这种语句时,不需要调用 new。
甚至还可以初始化一个匿名的数组:
new int[] {17,19,23,29,31,37};
这种表示法将创建一个新数组并利用括号中提供的值进行初始化,数组的大小就是初始值的个数。使用这种语法形式可以在不创建新变量的情况下重新初始化一个数组。例如:
smallPrimes = new int[] {17,19,23,29,31,37};
这是下列语句的简写形式:
int[] anonymous = {17,19,23,29,31,37};
smallPrimes = anonymous;

3.10.3 数组拷贝

在Java 中,允许将一个数组变量拷贝给另一个数组变量。这时,两个变量将引用同一个数组:
int[] luckyNumbers = smallPrimes;
luckyNumbers[5] = 12; //now mallPrimes[5] is alse 12
如果希望将一个数组的所有值拷贝到一个新的数组中去,就要使用Arrays 类的 copyTo 方法:
int[] copiedLuckyNumbers = Arrays.copyOf(luckyNumbers, luckyNumbers.length);
第2个参数是新数组的长度。这个方法通常用来增加数组的大小:
luckyNumbers = Arrays.copyOf(luckyNumbers, 2 * luckyNumbers.length);
如果数组类型是数值型,那么多余的元素将被赋值为0;如果数组元素是布尔型,则将赋值为false。相反,如果长度小于原始数组的长度,则只拷贝最前面的数据元素。

3.10.4 命令行参数

前面已经看到多个使用 Java 数组的示例。 每一个 Java 应用程序都有一个带 String arg[] 参数的 main 方法。 这个参数表明 main 方法将接收一个字符串数组, 也就是命令行参数。
例如,看一看下面这个程序:
public class Message {
    public static void main(String[] args)
    {
        if (args[0].equals("-h"))
            System.out.print("Hello,");
        else if (args[0].equals("-g"))
            System.out.print("Goodbye,");
        //print the other command-line arguments
        for (int i = 1; i < args.length; i++)
            System.out.print(" " + args[i]);
        System.out.print("!");
    }
}
如果使用下面这种形式运行这个程序: java Message -g cruel world
args 数组将包含下列内容:
args[0]:"-g"
args[1]:"cruel"
args[2]:"world"
这个程序将显示下列信息:Goodbye,cruel world!
3.10.5 数组排序
要想对数值型数组进行排序,可以使用Arrays 类中的 sort 方法:
int[] a = new int[10000];
...
Arrays.sort(a);
这个方法使用了优化的快速排序算法。快速排序算法对于大多数数据集合来说都是效率比较高的。Array 类还提供了几个使用很便捷的方法。
程序清单 3-7 中的程序用到了数组,它产生一个抽彩游戏中的随机数组合。假如抽彩是从49 个数值中抽取 6个,那么程序可能的输出结果为:
Bet the following combination. It'll make you rich!
4
7
8
19
30
44
要想选择这样一个随机的数值集合,就要首先将数值,1,2,...,n 存入数组 numbers 中:














  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值