当前位置: 100md首页 > 医学版 > 医学资料 > 资料下载2021
编号:4751
Java编程思维.pdf
http://www.100md.com 2020年4月17日
第1页
第8页
第11页
第30页
第33页
第144页

    参见附件(3177KB,313页)。

     Java编程思维讲述了变量和运算符,输入和输出,void方法,条件和逻辑,对象数组等等关于JAVA知识的书籍,简单易懂的语言更好理解,带你走进JAVA编程。

    Java编程思维内容

    本书旨在教你像计算机科学家那样思考,主要用代码示例诠释计算机科学概念。从最基本的概念入手,每个术语都在首次使用时给出详尽的定义;循序渐进地介绍新概念;将内容广泛的主题分成多个部分,并分多章介绍。主要从从小问题和基本算法着手,逐步过渡到面向对象设计。

    书籍作者简介

    Allen B. Downey

    欧林学院的计算机教授。曾任教于韦尔斯利女子学院、科尔比学院和加州大学伯克利分校;拥有加州大学伯克利分校计算机博士学位以及麻省理工学院学士和硕士学位。

    Chris Mayfield

    詹姆斯麦迪逊大学的计算机助理教授,致力于计算机教育和职业发展的研究;拥有普渡大学计算机博士学位以及犹他大学计算机和德语学士学位。

    调试代码

    最好能在计算机前阅读本书,因为这样你就可以一边阅读一边尝试其中的示例。本书中的很多示例可直接在DrJava的Interactions窗格(见附录A)中运行,但如果将代码存储到源代码文件中,则更容易对其修改再运行。

    每当你使用新功能时,都应该尝试故意犯些错误。例如,在HelloWorld程序中,如果遗漏一个引号,结果将如何呢?如果两个引号都遗漏了,结果将如何呢?如果println拼写得不正确,结果又将如何呢?这些尝试不仅有助于牢记学过的知识,还有助于调试程序,因为你将知道各种错误消息意味着什么。现在故意犯错胜过以后无意间犯错。

    调试犹如实验科学:一旦对出问题的地方有所感觉,就修改程序并再次运行。如果假设没错,你就能预测修改后的结果,从而离程序正确运行更近一步;如果假设有误,你就必须作出新的假设。

    编程和调试必须齐头并进。不能先随便编写大量的代码,再通过反复调试来确保它们能够正确地运行;相反,应先编写少量可正确运行的代码,再逐步修改和调试,最终得到一个提供所需功能的程序。这样的方式可以确保在任何时候都有可运行的程序,从而更容易隔离错误。

    Linux操作系统淋漓尽致地展示了这种原则。这个操作系统现在包含数百万行的代码,但最初只是一个简单的程序,LinusTorvalds用它来研究Intel80386芯片。正如LarryGreenfield在LinxuUser'sGuide中指出的,Linux是LinusTorvalds早期开发的项目之一,最初只是一个决定打印AAAA还是BBBB的程序,后来才演变为Linux。

    最后,编程可能引发强烈的情绪。面对棘手的bug而束手无策时,你可能会感到愤怒、沮丧或窘迫。别忘了,并非只有你这样,大多数乃至所有程序员都有类似的经历;不要犹豫,赶快向朋友求助吧!

    目录

    第 1 章 编程之道

    1.1 何为编程

    1.2 何为计算机科学

    1.3 编程语言

    1.4 Hello World程序

    1.5 显示字符串

    1.6 转义序列

    1.7 设置代码格式

    1.8 调试代码

    1.9 术语表

    1.10 练习

    第 2 章 变量和运算符

    2.1 声明变量

    2.2 赋值

    2.3 状态图

    2.4 显示变量

    2.5 算术运算符

    2.6 浮点数

    2.7 舍入误差

    2.8 字符串运算符

    2.9 组合

    2.10 错误类型

    2.11 术语表

    2.12 练习

    第 3 章 输入和输出

    3.1 System类

    3.2 Scanner类

    3.3 程序结构

    3.4 英寸到厘米的转换

    3.5 字面量和常量

    3.6 设置输出的格式

    3.7 厘米到英寸的转换

    3.8 求模运算符

    3.9 整合

    3.10 Scanner类的bug

    3.11 术语表

    3.12 练习

    第 4 章 void 方法

    4.1 Math类的方法

    4.2 再谈组合

    4.3 添加方法

    4.4 执行流程

    4.5 形参和实参

    4.6 多个形参

    4.7 栈图

    4.8 阅读文档

    4.9 编写文档

    4.10 术语表

    4.11 练习

    第 5 章 条件和逻辑

    5.1 关系运算符

    5.2 逻辑运算符

    5.3 条件语句

    5.4 串接和嵌套

    5.5 标志变量

    5.6 return语句

    5.7 验证输入

    5.8 递归方法

    5.9 递归栈图

    5.10 二进制数

    5.11 术语表

    5.12 练习

    第 6 章 值方法

    6.1 返回值

    6.2 编写方法

    6.3 方法组合

    6.4 重载

    6.5 boolean方法

    6.6 Javadoc标签

    6.7 再谈递归

    6.8 姑且相信

    6.9 再举一个例子

    6.10 术语表

    6.11 练习

    第 7 章 循环

    7.1 while语句

    7.2 生成表格

    7.3 封装和泛化

    7.4 再谈泛化

    7.5 for语句

    7.6 do-while循环

    7.7 break和continue

    7.8 术语表

    7.9 练习

    第 8 章 数组

    8.1 创建数组

    8.2 访问元素

    8.3 显示数组

    8.4 复制数组

    8.5 数组的长度

    8.6 数组遍历

    8.7 随机数

    8.8 遍历和计数

    8.9 生成直方图

    8.10 改进的for循环

    8.11 术语表

    8.12 练习

    第 9 章 字符串

    9.1 字符

    9.2 字符串是不可修改的

    9.3 字符串遍历

    9.4 子串

    9.5 方法indexOf

    9.6 字符串比较

    9.7 设置字符串的格式

    9.8 包装类

    9.9 命令行实参

    9.10 术语表

    9.11 练习

    第 10 章 对象

    10.1 Point对象

    10.2 属性

    10.3 将对象用作参数

    10.4 将对象作为返回类型

    10.5 可修改的对象

    10.6 指定别名

    10.7 关键字null

    10.8 垃圾收集

    10.9 类图

    10.10 Java类库的源代码

    10.11 术语表

    10.12 练习

    第 11 章 类

    11.1 Time类

    11.2 构造函数

    11.3 再谈构造函数

    11.4 获取方法和设置方法

    11.5 显示对象

    11.6 方法toString

    11.7 方法equals

    11.8 时间相加

    11.9 纯方法和非纯方法

    11.10 术语表

    11.11 练习

    第 12 章 对象数组

    12.1 Card对象

    12.2 方法toString

    12.3 类变量

    12.4 方法compareTo

    12.5 Card对象是不可修改的

    12.6 Card数组

    12.7 顺序查找

    12.8 二分法查找

    12.9 跟踪代码

    12.10 递归版本

    12.11 术语表

    12.12 练习

    第 13 章 数组对象

    13.1 Deck类

    13.2 洗牌

    13.3 选择排序

    13.4 合并排序

    13.5 方法subdeck

    13.6 方法merge

    13.7 添加递归

    13.8 术语表

    13.9 练习

    第 14 章 包含其他对象的对象

    14.1 Deck和手里的牌

    14.2 CardCollection

    14.3 继承

    14.4 发牌

    14.5 Player类

    14.6 Eights类

    14.7 类之间的关系

    14.8 术语表

    14.9 练习

    Java编程思维截图

    书名:Java编程思维

    作者:[美] Allen B. Downey Chris Mayfield

    译者:袁国忠

    ISBN:978-7-115-44015-0

    本书由北京图灵文化发展有限公司发行数字版。版权所有,侵权必究。

    091507240605ToBeReplacedWithUserId

    权措施,并可能追究法律责任。

    如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维

    我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

    本书内容。

    您购买的图灵电子书仅供您个人使用,未经授权,不得以任何方式复制和传播

    1.5 显示字符串

    1.4 Hello World程序

    1.3 编程语言

    1.2 何为计算机科学

    1.1 何为编程

    第 1 章 编程之道

    电子书

    致谢

    联系我们

    Safari? Books Online

    排版约定

    使用代码示例

    附录

    面向对象编程

    编写理念

    前言

    业界评论

    O'Reilly Media, Inc. 介绍

    版权声明1.6 转义序列

    1.7 设置代码格式

    1.8 调试代码

    1.9 术语表

    1.10 练习

    第 2 章 变量和运算符

    2.1 声明变量

    2.2 赋值

    2.3 状态图

    2.4 显示变量

    2.5 算术运算符

    2.6 浮点数

    2.7 舍入误差

    2.8 字符串运算符

    2.9 组合

    2.10 错误类型

    2.11 术语表

    2.12 练习

    第 3 章 输入和输出3.1 System类

    3.2 Scanner类

    3.3 程序结构

    3.4 英寸到厘米的转换

    3.5 字面量和常量

    3.6 设置输出的格式

    3.7 厘米到英寸的转换

    3.8 求模运算符

    3.9 整合

    3.10 Scanner类的bug

    3.11 术语表

    3.12 练习

    第 4 章 void 方法

    4.1 Math类的方法

    4.2 再谈组合

    4.3 添加方法

    4.4 执行流程

    4.5 形参和实参

    4.6 多个形参4.7 栈图

    4.8 阅读文档

    4.9 编写文档

    4.10 术语表

    4.11 练习

    第 5 章 条件和逻辑

    5.1 关系运算符

    5.2 逻辑运算符

    5.3 条件语句

    5.4 串接和嵌套

    5.5 标志变量

    5.6 return语句

    5.7 验证输入

    5.8 递归方法

    5.9 递归栈图

    5.10 二进制数

    5.11 术语表

    5.12 练习

    第 6 章 值方法6.1 返回值

    6.2 编写方法

    6.3 方法组合

    6.4 重载

    6.5 boolean方法

    6.6 Javadoc标签

    6.7 再谈递归

    6.8 姑且相信

    6.9 再举一个例子

    6.10 术语表

    6.11 练习

    第 7 章 循环

    7.1 while语句

    7.2 生成表格

    7.3 封装和泛化

    7.4 再谈泛化

    7.5 for语句

    7.6 do-while循环

    7.7 break和continue7.8 术语表

    7.9 练习

    第 8 章 数组

    8.1 创建数组

    8.2 访问元素

    8.3 显示数组

    8.4 复制数组

    8.5 数组的长度

    8.6 数组遍历

    8.7 随机数

    8.8 遍历和计数

    8.9 生成直方图

    8.10 改进的for循环

    8.11 术语表

    8.12 练习

    第 9 章 字符串

    9.1 字符

    9.2 字符串是不可修改的

    9.3 字符串遍历9.4 子串

    9.5 方法indexOf

    9.6 字符串比较

    9.7 设置字符串的格式

    9.8 包装类

    9.9 命令行实参

    9.10 术语表

    9.11 练习

    第 10 章 对象

    10.1 Point对象

    10.2 属性

    10.3 将对象用作参数

    10.4 将对象作为返回类型

    10.5 可修改的对象

    10.6 指定别名

    10.7 关键字null

    10.8 垃圾收集

    10.9 类图

    10.10 Java类库的源代码10.11 术语表

    10.12 练习

    第 11 章 类

    11.1 Time类

    11.2 构造函数

    11.3 再谈构造函数

    11.4 获取方法和设置方法

    11.5 显示对象

    11.6 方法toString

    11.7 方法equals

    11.8 时间相加

    11.9 纯方法和非纯方法

    11.10 术语表

    11.11 练习

    第 12 章 对象数组

    12.1 Card对象

    12.2 方法toString

    12.3 类变量

    12.4 方法compareTo12.5 Card对象是不可修改的

    12.6 Card数组

    12.7 顺序查找

    12.8 二分法查找

    12.9 跟踪代码

    12.10 递归版本

    12.11 术语表

    12.12 练习

    第 13 章 数组对象

    13.1 Deck类

    13.2 洗牌

    13.3 选择排序

    13.4 合并排序

    13.5 方法subdeck

    13.6 方法merge

    13.7 添加递归

    13.8 术语表

    13.9 练习

    第 14 章 包含其他对象的对象14.1 Deck和手里的牌

    14.2 CardCollection

    14.3 继承

    14.4 发牌

    14.5 Player类

    14.6 Eights类

    14.7 类之间的关系

    14.8 术语表

    14.9 练习

    附录 A 开发工具

    A.1 安装DrJava

    A.2 DrJava Interactions窗格

    A.3 命令行界面

    A.4 命令行测试

    A.5 运行Checkstyle

    A.6 使用调试器进行跟踪

    A.7 用JUnit进行测试

    A.8 术语表

    附录 B Java 2D 图形B.1 创建图形

    B.2 Graphics类的方法

    B.3 绘图示例

    B.4 术语表

    B.5 练习

    附录 C 调试

    C.1 编译时错误

    C.1.1 编译器显示大量的错误消息

    C.1.2 编译器显示怪异的错误消息,怎么都消除不掉

    C.1.3 怎么做都无法让程序通过编译

    C.1.4 按编译器说的做了,但还是不管用

    C.2 运行时错误

    C.2.1 程序挂起

    C.2.2 程序运行时出现异常

    C.2.3 添加了很多打印语句,输出都泛滥成灾了

    C.3 逻辑错误

    C.3.1 程序不管用

    C.3.2 冗长表达式的结果出乎意料

    C.3.3 方法的返回值出乎意料C.3.4 打印语句什么都不显示

    C.3.5 陷入了绝境,无法自拔

    C.3.6 必须得有人帮我

    C.3.7 终于找到bug了!

    作者简介

    封面简介版权声明

    2016 Allen B. Downey and Chris Mayfield.

    Simplified Chinese Edition, jointly published by O'Reilly

    Media, Inc. and Posts Telecom Press, 2016. Authorized translation

    of the English edition, 2016 O'Reilly Media, Inc., the owner of all

    rights to publish and sell the same.

    All rights reserved including the rights of reproduction in

    whole or in part in any form.

    英文原版由 O'Reilly Media, Inc. 出版,2016。

    简体中文版由人民邮电出版社出版,2017。英文原版的翻译得到 O'Reilly

    Media, Inc. 的授权。此简体中文版的出版和销售得到出版权和销售权的所有者

    ——O'Reilly Media, Inc. 的许可。

    版权所有,未得书面许可,本书的任何部分和全部不得以任何形式重制。O'Reilly Media, Inc. 介绍

    O'Reilly Media 通过图书、杂志、在线服务、调查研究和会议等方式传播创

    新知识。自 1978 年开始,O'Reilly 一直都是前沿发展的见证者和推动者。超级

    极客们正在开创着未来,而我们关注真正重要的技术趋势——通过放大那些“细微

    的信号”来刺激社会对新科技的应用。作为技术社区中活跃的参与者,O'Reilly

    的发展充满了对创新的倡导、创造和发扬光大。

    O'Reilly 为软件开发人员带来革命性的“动物书”;创建第一个商业网站

    (GNN);组织了影响深远的开放源代码峰会,以至于开源软件运动以此命名;创立

    了 Make 杂志,从而成为 DIY 革命的主要先锋;公司一如既往地通过多种形式缔

    结信息与人的纽带。O'Reilly 的会议和峰会集聚了众多超级极客和高瞻远瞩的商

    业领袖,共同描绘出开创新产业的革命性思想。作为技术人士获取信息的选择,O'Reilly 现在还将先锋专家的知识传递给普通的计算机用户。无论是通过书籍出

    版、在线服务或者面授课程,每一项 O'Reilly 的产品都反映了公司不可动摇的理

    念——信息是激发创新的力量。

    业界评论

    “O'Reilly Radar 博客有口皆碑。”

    ——Wired

    “O'Reilly 凭借一系列(真希望当初我也想到了)非凡想法建立了数百万美

    元的业务。”

    ——Business 2.0

    “O'Reilly Conference 是聚集关键思想领袖的绝对典范。”

    ——CRN

    “一本 O'Reilly 的书就代表一个有用、有前途、需要学习的主题。”——Irish Times

    “Tim 是位特立独行的商人,他不光放眼于最长远、最广阔的视野,并且切

    实地按照 Yogi Berra 的建议去做了:‘如果你在路上遇到岔路口,走小路(岔

    路)。’回顾过去,Tim 似乎每一次都选择了小路,而且有几次都是一闪即逝的

    机会,尽管大路也不错。”

    ——Linux Journal前言

    本书是针对初学者编写的计算机科学和编程入门教程。从最基本的概念入手,每个术语都在首次使用时给出详尽的定义;循序渐进地介绍新概念;将内容广泛的

    主题(如递归和面向对象编程)分成多个部分,并分多章介绍。

    本书简明扼要,每章都只有十几页的篇幅,涵盖了一周的大学课程内容。本书

    无意全面介绍 Java,只是想让读者了解基本的编程结构和技巧。我们从小问题和基

    本算法着手,逐步过渡到面向对象设计,用计算机教学术语讲,本书采取的是“迟

    来的对象”法。

    编写理念

    本书是基于如下的指导原则编写的。

    每次一个概念。对于可能给初学者带来麻烦的主题,将其分成多个部分,让读者无需熟悉整个主题就能将新学到的概念付诸实践。

    兼顾 Java 和概念。本书的主要目的并非介绍 Java,而是用代码示例诠

    释计算机科学概念。大多数章节以 Java 的语言特性开头,以概念结束。

    简明扼要。本书的一个重要目标是使篇幅够小,好让读者一个学期就能读

    完并搞懂本书内容。

    突出术语。尽可能少引入术语,并在首次使用时给出术语的详尽定义。在

    每章末尾,我们还将它们组织成了术语表。

    程序开发策略。程序编写策略有很多,包括自下而上、自上而下,等等。

    我们演示了开发程序的多种方法,让读者能够从中选择最适合的。

    多条学习曲线。要编写程序,得理解算法、熟悉编程语言,还要能够调试

    代码。本书始终在讨论这些内容,同时专辟了一个附录来总结调试建议。面向对象编程

    有些 Java 书一上来就介绍类和对象,有些则先介绍过程性编程,再逐步过渡

    到面向对象编程。

    Java 的很多面向对象功能都旨在解决以前的语言存在的问题,因此,其实现受

    到了这些历史原因的影响。对于这些功能,如果你不熟悉它们所能解决的问题,就

    很难理解。

    我们每次介绍一个概念,并尽可能将它讲清楚,让读者能够立即将学到的知识

    付诸实践。在这个前提之下,我们会尽早地介绍面向对象编程,因此,你不可能翻

    开本书就接触到这个主题。

    然而,如果不使用面向对象功能,根本就无法编写 Java 程序,哪怕是简单的

    Hello World 程序。对于有些功能,我们会在首次提及时简要地介绍一下,再在后

    面作更深入的讨论。

    本书几乎涵盖了“AP Java subset”中的每个主题,非常适合用来备考 AP

    计算机科学 A 考试(包括面向对象设计和实现)。我们的网站

    http:thinkjava.org 中列出了本书各节与 AP 课程最新描述的对应关系。

    附录

    本书适合按顺序逐章阅读,因为每一章都以前一章的内容为基础。本书还有三

    个附录,你可在任何时间阅读。

    附录 A(开发工具)

    编译、运行和调试 Java 代码的步骤随开发环境和操作系统而异,我们没

    有将这些细节放在正文中,因为这会分散读者的注意力。相反,我们专辟了附

    录 A,简要地介绍 DrJava——一个非常适合初学者使用的集成开发环境

    (interactive development environment,IDE),以及用于检查代码质

    量的 Checkstyle 和用于测试的 JUnit 等工具。附录 B(Java 2D 图形)

    Java 提供了处理图形和动画的库,这些主题可能对学生很有吸引力。这些

    库涉及面向对象功能,读者可能阅读完前 11 章才能完全理解,但可以很早地

    使用它们。

    附录 C(调试)

    有关调试的建议遍布全书,我们将这些调试建议收集到了附录 C 中。建议

    读者在阅读本书的过程中反复温习该附录。

    使用代码示例

    本书的示例代码大都可在 Git 仓库

    https:github.comAllenDowneyThinkJavaCode 中找到。Git 是一个

    版本控制系统,让你能够跟踪项目中的文件。受 Git 控制的文件集合称为“仓

    库”。

    GitHub 是一种托管服务,为 Git 仓库提供存储空间,还提供了方便的 Web

    界面。它提供了多种处理代码的方式。

    单击 Fork 键可以在 GitHub 上创建仓库的副本。如果你没有 GitHub

    账户,就需要创建一个。建立分支后,你便在 GitHub 上有自己的仓库了,可

    用它来跟踪你编写的代码。然后,还可以“克隆”这个仓库,即将文件的副本

    下载到计算机。

    你也可以在不建立分支的情况下克隆仓库。这样就不需要 GitHub 账户

    了,但也无法将所做的修改保存到 GitHub 中。

    如果你根本不想使用 Git,可用 GitHub 页面上的 Download ZIP 按钮

    下载 ZIP 格式的代码,也可通过链接

    http:www.tinyurl.comThinkJavaCodeZip 下载。

    克隆仓库或解压 ZIP 文件后,你将看到一个名为 ThinkJavaCode 的目录,其中包含与本书每章对应的子目录。本书中的所有示例都是用 Java SE Development Kit 8 开发和测试的。如

    果你使用的是更新的版本,这些示例也能正确地运行;但如果你使用的是更早的版

    本,有些示例可能无法正确地运行。

    排版约定

    本书使用了下列排版约定。

    楷体

    表示术语或重点强调的内容。

    等宽字体(constant width)

    表示程序片段,以及正文中出现的变量、函数名、数据库、数据类型、环

    境变量、语句和关键字等。

    加粗等宽字体(constant width bold)

    表示应该由用户输入的命令或其他文本。

    Safari? Books Online

    Safari Books Online(http:www.safaribooksonline.com)是应运而

    生的数字图书馆。它同时以图书和视频的形式出版世界顶级技术和商务作家的专业

    作品。技术专家、软件开发人员、Web 设计师、商务人士和创意专家等,在开展调

    研、解决问题、学习和认证培训时,都将 Safari Books Online 视作获取资料的

    首选渠道。

    对于组织团体、政府机构和个人,Safari Books Online 提供各种产品组合和灵活的定价策略。用户可通过一个功能完备的数据库检索系统访问 O'Reilly

    Media、Prentice Hall Professional、Addison-Wesley Professional、Microsoft Press、Sams、Que、Peachpit Press、Focal Press、Cisco

    Press、John Wiley Sons、Syngress、Morgan Kaufmann、IBM Redbooks、Packt、Adobe Press、FT Press、Apress、Manning、New Riders、McGraw-

    Hill、Jones Bartlett、Course Technology 以及其他几十家出版社的上千

    种图书、培训视频和正式出版之前的书稿。要了解 Safari Books Online 的更多

    信息,我们网上见。

    联系我们

    请把对本书的评价和问题发给出版社。

    美国:

    O'Reilly Media, Inc.

    1005 Gravenstein Highway North

    Sebastopol, CA 95472

    中国:

    北京市西城区西直门南大街 2 号成铭大厦 C 座 807 室(100035)

    奥莱利技术咨询(北京)有限公司

    O'Reilly 的每一本书都有专属网页,你可以在那儿找到本书的相关信息,包

    括勘误表、示例代码以及其他信息。本书的网站地址是:

    http:shop.oreilly.comproduct0636920041610.do

    对于本书的评论和技术性问题,请发送电子邮件到:

    bookquestions@oreilly.com要了解更多 O'Reilly 图书、培训课程、会议和新闻的信息,请访问以下网

    站:

    http:www.oreilly.com

    我们在 Facebook 的地址如下:http:facebook.comoreilly

    请关注我们的 Twitter 动态:http:twitter.comoreillymedia

    我们的 YouTube 视频地址如下:http:www.youtube.comoreillymedia

    致谢

    很多人提出了勘误和建议,这里要特别感谢他们的宝贵反馈!

    Ellen Hildreth 在威尔斯利女子学院讲授数据结构时,曾将本书作为补

    充读物,她指出了一大堆错误,还提出了一些很不错的建议。

    Tania Passfield 指出有些术语表包含了正文中没有出现的术语。

    Elizabeth Wiethoff 注意到 exp(—x2) 的级数展开不对,并审阅了本

    书的 Ruby 版。

    Matt Crawford 发来了一个包含大量勘误的文件。

    Chi-Yu Li 指出了一个代码示例的输入错误和编程错误。

    Doan Thanh Nam 更正了一个示例。

    Muhammad Saied 在将本书翻译成阿拉伯语的过程中发现了多个错误。

    Marius Margowski 发现有个代码示例存在不一致的问题。

    Leslie Klein 发现了 exp(—x2) 的级数展开中的另一个错误,指出了

    Card 数组示意图中的输入错误,并就如何让几个练习更清晰提出了很有帮助的

    建议。Micah Lindstrom 指出了六七个输入错误,并给出了更正建议。

    James Riely 将本书电子版从 LaTeX 格式转换成了 Sphinx 格

    式:http:fpl.cs.depaul.edujrielythinkapjava。

    Peter Knaggs 将本书转换成了 C

    版:http:www.rigwit.co.ukthinksharp。

    Heidi Gentry-Kolen 拍摄了多个以本书为教材的教学视

    频:https:www.youtube.comuserdigipipeline。

    这里要特别感谢技术审阅人 Blythe Samuels、David Wisneski 和

    Stephen Rose。他们找出了错误,提出了很多宝贵建议,让本书的质量得到了极大

    的提高。

    另外,感谢以下人员发现并指出了输入错误:Stijn Debrouwere、Guy

    Driesen、Andai Velican、Chris Kuszmaul、Daniel Kurikesu、Josh

    Donath、Rens Findhammer、Elisa Abedrapo、Yousef BaAfif、Bruce

    Hill、Matt Underwood、Isaac Sultan、Dan Rice、Robert Beard 和

    Daniel Pierce。

    如果你有其他建议或看法,请发送到 feedback@greenteapress.com。

    电子书

    扫描如下二维码,即可购买本书电子书。第 1 章 编程之道

    本书旨在教你像计算机科学家那样思考。这种思维方式兼具数学、工程和自然

    科学的优点:计算机科学家像数学家那样使用规范的语言来描绘概念,具体地说就

    是计算;像工程师那样设计,将各个部分组装成系统并权衡不同的解决方案;像科

    学家那样观察复杂系统的行为,进而作出假设并进行验证。

    对计算机科学家来说,最重要的技能是解决问题(problem solving)。这包

    括系统地阐述问题、创造性地提出解决方案,以及清晰而准确地描述解决方案。实

    践表明,学习编程为获得解决问题的技能提供了极佳的机会,这正是本章名为“编

    程之道”的原因所在。

    一方面,你将学习编程,这本身就是一项很有用的技能;另一方面,你将把编

    程作为达到目的的手段。随着不断往下阅读,目的将变得更加清晰。

    1.1 何为编程

    程序(program)由一系列指令组成,指定了如何执行计算。这里的计算可能

    是数学计算,如求解方程组或找出多项式的根,也可能是符号计算,如在文档中搜

    索并替换文本或编译程序(真够奇怪的,编译程序竟然也是计算)。虽然细节因语

    言而异,但几乎所有语言都支持一些基本指令。

    输入

    从键盘、文件、传感器或其他设备获取数据。

    输出

    在屏幕上显示数据,或者将数据发送给文件或其他设备。

    数学运算

    执行基本的数学运算,如加法和除法。决策

    检查特定的条件,并根据检查结果执行相应的代码。

    重复

    反复执行某种操作,但通常每次执行时都略有不同。

    信不信由你,这几乎就是程序的全部内容。你使用的每个程序都由类似于上面

    的小指令组成,不管它有多复杂。因此,你可将编程(programming)视为这样的

    过程,即将复杂而庞大的任务分解为较小的子任务。不断重复这个过程,直到分解

    得到的子任务足够简单,用计算机提供的基本指令就能完成。

    1.2 何为计算机科学

    对编程而言,最有趣的一个方面是决定如何解决特定的问题,尤其是问题存在

    多种解决方案时。例如,对数字列表进行排序的方法很多,其中每种方法都有其优

    点。要确定哪种方法是特定情况下的最佳方法,你必须具备规范地描述和分析解决

    方案的技能。

    计算机科学(computer science)就是算法科学,包括找出算法并对其进行

    分析。算法(algorithm)由一系列指定如何解决问题的步骤组成。有些算法的速

    度比其他算法快,有些使用的计算机内存更少。面对以前没有解决过的问题,你在

    学着找出算法的同时,也将学习如何像计算机科学家那样思考。

    设计算法并编写代码很难,也很容易出错。由于历史的原因,编程错误被称为

    bug,而找出并消除编程错误的过程被称为调试(debugging)。通过学习调试程

    序,你将获得解决新问题的技能。面临出乎意料的错误时,需要创造性思维。

    虽然调试可能令人沮丧,但它是计算机编程中有趣且挑战智商的部分。从某种

    程度上来说,调试犹如侦破工作:必须根据掌握的线索猜想出引发结果的过程和事

    件。在有些情况下,在考虑如何修复程序以及改善其性能的过程中,还能发现新的

    算法。1.3 编程语言

    本书要介绍的编程语言是 Java,这是一种高级语言(high-level

    language)。你可能还听说过其他高级语言,如 Python、C、C++、Ruby 和

    JavaScript。

    要想运行用高级语言编写的程序,必须将其转换为低级语言(low-level

    language),即“机器语言”。这种转换需要一定的时间,这是高级语言的一个小

    小的缺点,但高级语言有两个优点。

    用高级语言编程容易得多:编写程序所需要的时间更短,程序更简洁、更

    容易理解,同时更容易确保程序正确无误。

    高级语言是可移植的(portable),这意味着用高级语言编写的程序只需

    做少量修改甚至无需修改,就可在不同类型的计算机上运行。用低级语言编写

    的程序只能在一种计算机上运行,这种程序必须重写才能在其他计算机上运

    行。

    有两种将高级语言转换为低级语言的程序:解释器和编译器。解释器

    (interpreter)读取并执行用高级语言编写的程序,这意味着程序怎么说它就怎

    么做。它每次处理程序的一小部分,即交替地读取代码行并执行计算。图 1-1 展示

    了解释器的结构。

    图 1-1:解释型语言是如何执行的

    相反,编译器(compiler)读取并转换整个程序,然后才开始运行程序。在这

    种情况下,用高级语言编写的程序称为源代码(source code),而转换得到的程

    序称为目标代码(object code)或可执行程序(executable)。程序编译后可反复执行,无需在每次执行前都进行转换。因此,编译型程序的运行速度通常比解释

    型程序更快。

    Java 既是解释型的又是编译型的。Java 编译器不将程序直接转换为机器语

    言,而是生成字节码(byte code)。字节码类似于机器语言,解释起来既轻松又

    快捷,同时也是可移植的,即可在一台机器上编译程序,在另一台机器上运行生成

    的字节码。运行字节码的解释器被称为 Java 虚拟机(Java Virtual Machine,JVM)。

    图 1-2:Java 程序的编译和运行过程

    图 1-2 展示了这个过程包含的步骤。这个过程看似复杂,但在大多数程序开发

    环境中,这些步骤都是自动完成的:通常只需按一下按钮或输入简单的命令,就能

    编译并运行程序。然而,知道幕后执行了哪些步骤很重要,这样就可以在出现问题

    时找出问题所在。

    1.4 Hello World程序

    传统上,学习一门新的编程语言时,通常先编写一个名为 Hello World 的程

    序,它所做的只是在屏幕上显示“Hello, World!”。用 Java 编写时,这个程序

    与下面的类似:

    public class Hello {

    public static void main(String[] args) {

    生成一些简单的输出

    System.out.println(Hello, World!);

    }

    }这个程序运行时显示如下内容:

    Hello, World!

    注意,输出中没有引号。

    Java 程序由类定义和方法定义组成,而其中的方法由语句(statement)组

    成。语句是一行执行基本操作的代码。在 Hello World 程序中,这是一条打印语

    句(print statement),在屏幕上显示一条消息:

    System.out.println(Hello, World!);

    System.out.println 在屏幕上显示结果,其中的 println 表示“打印一

    行”。令人迷惑的是,打印既可以表示“在屏幕上显示”,也可以表示“发送到打

    印机”。在本书中,表示输出到屏幕上时,我们尽可能说“显示”。与大多数语句

    一样,打印语句也以分号(;)结尾。

    Java 是区分大小写的,这意味着大写和小写是不同的。在前面的示例

    中,System 的首字母必须大写,使用 system 或 SYSTEM 都行不通。

    方法(method)是一系列命名的语句。前面的程序定义了一个名为 main 的方

    法:

    public static void main(String[] args)

    方法 main 比较特殊:程序运行时,首先执行方法 main 中的第一条语句,并

    在执行完这个方法的最后一条语句后结束。在本书的后文中,你将看到定义了多个

    方法的程序。

    类(class)是方法的集合。前面的程序定义了一个名为 Hello 的类。你可以

    随便给类命名,但根据约定,类名的首字母应大写。类必须与其所属的文件同名,因此前面的类必须存储在文件 Hello.java 中。Java 用大括号({ 和 })编组。在 Hello.java 中,外面的大括号包含类定

    义,而里面的大括号包含方法定义。

    以双斜线()开头的行是注释(comment),它用自然语言编写的文本解释

    代码。编译器遇到 时,将忽略随后到行尾的所有内容。注释对程序的执行没有

    任何影响,但可以让其他程序员(还有未来的你自己)更容易地明白你要做什么。

    1.5 显示字符串

    方法 main 中可包含任意条语句。例如,要显示多行输出,你可以像下面这样

    做:

    public class Hello {

    public static void main(String[] args) {

    生成一些简单的输出

    System.out.println(Hello, World!); 第一行

    System.out.println(How are you?); 第二行

    }

    }

    这个示例表明,除独占一行的注释外,还可在行尾添加注释。

    用引号括起的内容称为字符串(string),因为它们包含一系列串在一起的字

    符。字符包括字母、数字、标点、符号、空格、制表符,等等。

    System.out.println 在指定的字符串末尾添加了一个特殊字符——换行符

    (newline),其作用是移到下一行开头。如果你不想在末尾添加换行符,可用

    print 代替 println:

    public class Goodbye {

    public static void main(String[] args) {

    System.out.print(Goodbye, );

    System.out.println(cruel world);

    }}

    在这个示例中,第一条语句没有添加换行符,因此输出只有一行:Goodbye,cruel world。请注意,第一个字符串末尾有一个空格,这也包含在输出中。

    1.6 转义序列

    可用一行代码显示多行输出,只需告诉 Java 在哪里换行就可以了:

    public class Hello {

    public static void main(String[] args) {

    System.out.print(Hello!\nHow are you doing?\n);

    }

    }

    上述代码的输出为两行,每行都以换行符结尾:

    Hello!

    How are you doing?

    \n 是一个转义序列(escape sequence),表示特殊字符的字符序列。反斜

    线让你能够对字符串的内容进行转义。请注意,\n 和 How 之间没有空格;如果在

    这里添加一个空格,第二行输出的开头将会是一个空格。

    转义序列的另一个常见用途是在字符串中包含引号。由于双引号标识字符串的

    开头和结尾,因此,要想在字符串中包含双引号,必须用反斜线对其进行转义。

    System.out.println(She said \Hello!\ to me.);

    结果如下:

    She said Hello! to me.表1-1:常见的转义序列

    \n 换行符

    \t 制表符

    \ 双引号

    \\ 反斜线

    1.7 设置代码格式

    在 Java 程序中,有些空格是必不可少的。例如,不同的单词之间至少得有一

    个空格,因此下面的程序是不合法的:

    publicclassGoodbye{

    publicstaticvoidmain(String[] args) {

    System.out.print(Goodbye, );

    System.out.println(cruel world);

    }

    }

    但其他空格大都是可有可无的。例如,下面的程序完全合法:

    public class Goodbye {

    public static void main(String[] args) {

    System.out.print(Goodbye, );

    System.out.println(cruel world);

    }

    }

    换行也是可选的,因此可将前面的代码编写如下:

    public class Goodbye { public static void main(String[] args)

    { System.out.print(Goodbye, ); System.out.println

    (cruel world);}}

    行的程序,从而更容易隔离错误。

    试,最终得到一个提供所需功能的程序。这样的方式可以确保在任何时候都有可运

    保它们能够正确地运行;相反,应先编写少量可正确运行的代码,再逐步修改和调

    编程和调试必须齐头并进。不能先随便编写大量的代码,再通过反复调试来确

    设有误,你就必须作出新的假设。

    如果假设没错,你就能预测修改后的结果,从而离程序正确运行更近一步;如果假

    调试犹如实验科学:一旦对出问题的地方有所感觉,就修改程序并再次运行。

    犯错胜过以后无意间犯错。

    过的知识,还有助于调试程序,因为你将知道各种错误消息意味着什么。现在故意

    呢?如果 println 拼写得不正确,结果又将如何呢?这些尝试不仅有助于牢记学

    序中,如果遗漏一个引号,结果将如何呢?如果两个引号都遗漏了,结果将如何

    每当你使用新功能时,都应该尝试故意犯些错误。例如,在 Hello World 程

    运行,但如果将代码存储到源代码文件中,则更容易对其修改再运行。

    例。本书中的很多示例可直接在 DrJava 的 Interactions 窗格(见附录 A)中

    最好能在计算机前阅读本书,因为这样你就可以一边阅读一边尝试其中的示

    1.8 调试代码

    读本书的过程中,你可能时不时地想要回过头来阅读它们。

    这些指南提及了本书还未介绍的 Java 功能,因此你现在可能看不懂,但在阅

    http:google.github.iostyleguidejavaguide.html。

    Google 就发布了针对开源项目的 Java 编码标准,其网址为

    从事大量软件开发工作的组织通常会制定严格的源代码格式设置指南,例如,代码。

    DrJava(参见附录 A)中,可以按 Ctrl+A 选择所有的代码,再按 Tab 键来缩进

    很多编辑器都自动设置源代码的格式:以一致的方式缩进和换行。例如,在

    格都很重要,使用它们可让程序更容易理解,发生错误时也更容易查找。

    这也是可行的,但程序阅读起来更难了。要以直观的方式组织程序,换行和空Linux 操作系统淋漓尽致地展示了这种原则。这个操作系统现在包含数百万行

    的代码,但最初只是一个简单的程序,Linus Torvalds 用它来研究 Intel

    80386 芯片。正如 Larry Greenfield 在 Linxu User's Guide 中指出的,Linux 是 Linus Torvalds 早期开发的项目之一,最初只是一个决定打印 AAAA

    还是 BBBB 的程序,后来才演变为 Linux。

    最后,编程可能引发强烈的情绪。面对棘手的 bug 而束手无策时,你可能会感

    到愤怒、沮丧或窘迫。别忘了,并非只有你这样,大多数乃至所有程序员都有类似

    的经历;不要犹豫,赶快向朋友求助吧!

    1.9 术语表

    对于每个术语,本书都尽可能在首次用到时作出定义。同时,我们会在每章末

    尾按出现顺序列出涉及的新术语及其定义。如果你花点时间研究以下术语表,后面

    的内容阅读起来将更加轻松。

    解决问题

    明确地描述问题、找到并描述解决方案的过程。

    程序

    一系列的指令,指定了如何在计算机上执行任务。

    编程

    用问题解决技能创建可执行的计算机程序。

    计算机科学

    科学而实用的计算方法及其应用。

    算法

    解决问题的流程或公式,可以涉及计算机,也可以不涉及。

    可执行代码

    编译器转换程序后得到的输出。

    目标代码

    用高级语言编写的、未编译的程序。

    源代码

    将用高级语言编写的程序一次性转换为低级语言,供以后执行。

    编译

    到的指令。

    指运行用高级语言编写的程序,即每次转换其中的一行并立即执行转换得

    解释

    程序能够在多种计算机上运行。

    可移植

    计算机能够轻松运行的编程语言,也叫“机器语言”或“汇编语言”。

    低级语言

    人类能够轻松读写的编程语言。

    高级语言

    找出并消除错误的过程。

    调试

    程序中的错误。

    bug可在特定硬件上执行的目标代码的别名。

    字节码

    Java 程序使用的一种特殊目标代码,类似于低级语言,但像高级语言一样

    是可移植的。

    语句

    程序的一部分,指定了算法中的一个步骤。

    打印语句

    将输出显示到屏幕上的语句。

    方法

    命名的语句序列。

    类

    就目前而言,指的是一系列相关的方法。(后面你将看到,类并非只包含

    方法。)

    注释

    程序的一部分,包含有关程序的信息,但对程序的运行没有任何影响。

    字符串

    一系列字符,是一种基本的文本数据类型。

    换行符

    标识文本行尾的特殊字符。

    转义序列在字符串中用于表示特殊字母的编码序列。

    1.10 练习

    每章末尾都有练习,只需要利用在该章学到的知识就能完成。强烈建议你尝试

    完成每个练习,光阅读是学不会编程的,得实践才行。

    要想编译并运行 Java 程序,需要下载并安装一些工具。这样的工具很多,但

    我们推荐 DrJava——一个非常适合初学者使用的“集成开发环境”。有关如何安

    装 DrJava,请参阅附录 A 的 A.1 节。

    本章的示例代码位于仓库 ThinkJavaCode 的目录 ch01 中,有关如何下载这

    个仓库,请参阅前言中的“使用示例代码”一节。做以下的练习前,建议你先编译

    并运行本章的示例。

    练习1-1

    计算机科学家有个毛病,喜欢赋予常见的英语单词以新的义项。例如,在英语

    中 statement 和 comment 是同义词,但在程序中它们的含义不同。

    (1) 在计算机行话中,语句和注释有何不同?

    (2) 说程序是可移植的是什么意思?

    (3) 在普通英语中,单词 compile 是什么意思?

    (4) 何为可执行程序(executable)?为何这个单词被用作名词?

    每章末尾的术语表旨在突出计算机科学中有特殊含义的单词和短语。看到熟悉

    的单词时,千万不要理所应当地认为你知道它们的含义!

    练习1-2

    接着往下读之前,请先搞清楚如何编译和运行 Java 程序。有些编程环境提供

    了类似于本章 Hello World 程序的示例程序。(1) 输入 Hello World 程序的代码,再编辑并运行它。

    (2) 添加一条打印语句,在“Hello, World!”后面再显示一条诙谐的消息,如“How are you?”,然后再编译并运行这个程序。

    (3) 在这个程序中添加一条注释(什么地方都可以),再编译并运行它。新添

    的注释应该对结果没有任何影响。

    这个练习看似微不足道,却为编写程序打下了坚实的基础。要想得心应手地调

    试程序,必须熟悉编程环境。

    在一些编程环境中,一不小心就不知道当前执行的是哪个程序了。你可能想调

    试某个程序,却不小心运行了另一个程序。为确保你看到的就是要运行的程序,一

    种简单的方法是添加并修改打印语句。

    练习1-3

    将能想到的错误都犯一次是个不错的注意,这样你就知道编译器都会显示哪些

    错误消息了。在有些情况下,编译器会准确地指出错误,你只需要修复指出的错误

    即可;但有时候,错误消息会将你引入歧途。调试多了就会培养出感觉,知道什么

    情况下该信任编译器,什么情况下只能自力更生。

    请在本章的 Hello World 程序中尝试下面每一种错误。每次修改后编译程序

    并阅读出现的错误消息,然后再修复错误。

    (1) 删除其中的一个左大括号。

    (2) 删除其中的一个右大括号。

    (3) 将方法名 main 改为 mian。

    (4) 删除单词 static。

    (5) 删除单词 public。

    (6) 删除单词 System。(7) 将 println 改为 Println。

    (8) 将 println 替换为 print。

    (9) 删除其中的一个括号;添加一个括号。第 2 章 变量和运算符

    本章将介绍如何用变量和运算符来编写语句。变量用于存储数字、单词等值,而运算符是执行计算的符号。另外,本章还将介绍三种编程错误,并提供其他的调

    试建议。

    2.1 声明变量

    编程语言最强大的功能之一是能够定义和操作变量(variable)。变量是存储

    值(value)的命名位置,其中的值可以是数字、文本、图像、声音和其他类型的数

    据。要存储值,得先声明变量。

    String message;

    这条语句是一个声明(declaration),因为它声明变量 message 的类型为

    String。每个变量都有类型(type),决定了它可以存储什么样的值。例如,类型

    为 int 的变量可存储整数,而类型为 char 的变量可存储字符。

    有些类型名的首字母大写,有些类型名的首字母小写。这种差别的含义将在后

    文中介绍,就目前而言,你只需要确保首字母大小写正确即可,因为没有类型

    Int,也没有类型 string。

    要声明整型变量,可用如下语法:

    int x;

    其中的 x 是一个随便指定的变量名。一般而言,使用的名称应指出变量的含

    义。例如,看到下面的声明,你可能就能猜出各个变量将存储什么值:

    String firstName;

    String lastName;int hour, minute;

    这个示例声明了四个变量,其中两个的类型为 String,另外两个的类型为

    int。根据约定,对于包含多个单词的变量名,如 firstName,应将每个单词的首

    字母大写,但第一个单词除外。变量名是区分大小写的,因

    此,firstName、firstname 和 FirstName 指的是不同的变量。

    这个示例还演示了在一行中声明多个同类变量的语法:hour 和 minute 都是

    int 变量。请注意,每条声明语句都以分号结尾。

    你可以随便给变量命名,但大约有 50 个被称为关键词(keyword)的保留词

    不能用作变量名。这些关键词包括 public、class、static、void 和 int,被

    编译器用来分析程序的结构。

    有关完整的关键词清单,请参阅

    http:docs.oracle.comjavasetutorialjavanutsandbolts_keywords.html

    但不必记住它们。大多数编程编辑器都提供了“语法突出”的功能,即用不同的颜

    色显示程序的不同部分。

    2.2 赋值

    声明变量后,即可用它们来存储值。为此,可用赋值(assignment)语句:

    message = Hello!; 给变量message指定值Hello!

    hour = 11; 将值11赋给变量hour

    minute = 59; 将变量minute的值设置为59

    这个示例包含三条赋值语句,其中的注释指出了三种解读赋值语句的方式。这

    里使用的术语可能令人迷惑,但涉及的概念简单易懂。

    当声明变量时,便创建了一个命名的存储位置。

    当给变量赋值时,便修改了它的值。一般而言,变量和赋给它的值必须是相同的类型。例如,你不能将字符串存储

    到变量 mintue 中,也不能将整数存储到变量 message 中。在本书的后文中,你

    将看到一些违反这条规则的示例。

    有些字符串看起来像是整数,但其实不是整数,这常常令人迷惑。例如,变量

    message 可包含字符串 123,这个字符串由字符 '1'、'2' 和 '3' 组成,与

    整数 123 不是同一码事。

    message = 123; 合法

    message = 123; 非法

    使用变量前,必须对其进行初始化(initialize,首次赋值)。你可以像前一

    个示例那样,先声明变量,再赋值;也可以在声明变量的同时给它赋值:

    String message = Hello!;

    int hour = 11;

    int minute = 59;

    2.3 状态图

    鉴于 Java 用符号“=”来赋值,你可能会认为语句 a=b 是一个相等声明,但

    事实并非如此!

    相等具有交换性,但赋值并非如此。例如,在数学中,如果 a=7,则 7=a;而

    在 Java 中,a=7; 是一条合法的赋值语句,但 7=a; 则不是,因为赋值语句的左

    边必须是变量名(存储位置)。

    另外,在数学中,相等声明在任何情况下都成立。如果当前 a=b,那么在任何

    情况下 a 和 b 都相等;而在 Java 中,赋值语句可能导致两个变量相等,但它们

    并不一定始终如此。

    int a = 5;

    int b = a; 现在a和b相等

    a = 3; a和b不再相等第三行代码修改了 a 的值,但没有修改 b 的值,因此它们不再相等。

    程序中的所有变量及其当前值一同组成了程序的状态(state)。图 2-1 显示

    了程序在上述三条语句运行后的状态。

    图 2-1:变量 a 和 b 的状态图

    显示程序状态的图被称为状态图(state diagram)。每个变量都用一个方框

    表示,方框内是变量的值,方框外是变量名。状态随程序的运行而变化,因此,应

    将状态图视为程序执行过程中特定时点的快照。

    2.4 显示变量

    可用 print 或 println 显示变量的值。下面的语句声明了一个名为

    firstLine 的变量,将值 Hello, again! 赋给它,并显示这个值:

    String firstLine = Hello, again!;

    System.out.println(firstLine);

    在说显示变量时,我们通常指的是显示变量的值。要显示变量的名称,必须将

    其用引号括起。

    System.out.print(The value of firstLine is );

    System.out.println(firstLine);这个示例的输出如下:

    The value of firstLine is Hello, again!

    不管变量的类型如何,显示其值的语法都相同。例如:

    int hour = 11;

    int minute = 59;

    System.out.print(The current time is );

    System.out.print(hour);

    System.out.print(:);

    System.out.print(minute);

    System.out.println(.);

    这个程序的输出如下:

    The current time is 11:59.

    要在同一行输出多个值,通常使用多条 print 语句,并在最后使用一条

    println 语句。千万不要忘了 println 语句!很多计算机都将来自 print 的输

    出存储起来,等遇到 println 后才将整行输出一次性显示出来。如果省略了

    println,程序可能在意想不到的时候显示存储的输出,甚至直到结束也不显示任

    何输出。

    2.5 算术运算符

    运算符(operator)是表示简单计算的符号,例如,加法运算符为 +,减法运

    算符为 -,乘法运算符为 ,而除法运算符为 。

    下面的程序将时间转换为分钟数:

    int hour = 11;int minute = 59;

    System.out.print(Number of minutes since midnight: );

    System.out.println(hour 60 + minute);

    在这个程序中,hour 60 + minute 是一个表达式(expression),表示

    计算将得到的单个值。这个程序运行时,每个变量都被替换为当前值,再执行运算

    符指定的计算。运算符使用的值称为操作数(operand)。

    前述示例的结果如下:

    Number of minutes since midnight: 719

    表达式通常由数字、变量和运算符组成。程序编译并执行时,表达式将变成单

    个值。

    例如,表达式 1 + 1 的值为 2。对于表达式 hour - 1,Java 将变量 hour

    替换为其当前值,因此这个表达式变成 11 - 1,结果为 10。对于表达式 hour

    60 + minute,其中的两个变量都被替换了,整个表达式变为 11 60 + 59。先

    执行乘法运算,因此这个表达式变为 660 + 59;再执行加法运算,结果为 719。

    加法、减法、乘法运算都与你的预期一样,但除法运算可能会让你感到意外。

    例如,下面的代码片段试图将分钟数转换为小时数:

    System.out.print(Fraction of the hour that has passed: );

    System.out.println(minute 60);

    其输出如下:

    Fraction of the hour that has passed: 0

    这样的结果令人感到迷惑。变量 minute 的值为 59,59 除以 60 的结果应

    为 0.98333,而不是 0。问题在于 Java 在两个操作数都为整数时执行“整数除

    法”,而根据设计,整数除法总是向下圆整,即便这里的结果更接近下一个整数时也是如此。

    一种替代方式是,计算百分比而不是小数:

    System.out.print(Percent of the hour that has passed: );

    System.out.println(minute 100 60);

    上述代码的输出如下:

    Percent of the hour that has passed: 98

    同样,结果也被向下圆整了,但至少离正确的答案更近了。

    2.6 浮点数

    一种更通用的解决方案是使用浮点(floating-point)数,它可用于表示小

    数,也可用于表示整数。在 Java 中,默认的浮点类型为 double,它指的是双精

    度浮点数。double 变量的声明和赋值语法与其他类型的变量相同:

    double pi;

    pi = 3.14159;

    只要有一个操作数为 double 值,Java 就执行“浮点除法”,因此,我们可

    以用如下方式来解决 2.5 节中的问题:

    double minute = 59.0;

    System.out.print(Fraction of the hour that has passed: );

    System.out.println(minute 60.0);

    输出如下:

    Fraction of the hour that has passed: 0.9833333333333333虽然浮点数很有用,但也可能让人感到迷惑。例如,Java 区分整数 1 和浮点

    数 1.0,即使它们看起来是同一个数字。它们属于不同的数据类型,而严格来说,你不能将一种类型的值赋给另一种类型的变量。

    下面的语句是非法的,因为左边是一个 int 变量,而右边是一个 double

    值:

    int x = 1.1; 编译错误

    这种规则很容易忘记,因为 Java 在很多情况下会自动转换类型:

    double y = 1; 合法,但这是一种糟糕的做法

    这个示例原本应该是非法的,但由于 Java 自动将 int 值 1 转换为 double

    值 1.0,使得这个示例变得合法了。这样的宽容是十分便利的,但常会给初学者带

    来问题,例如:

    double y = 1 3; 常见的错误

    你可能会认为变量 y 的值为 0.333333——一个合法的浮点值,但实际上其值

    为 0。右边的表达式将两个整数相除,因此 Java 执行整数除法,结果为 int 值

    0。这个结果被转换为 double 值 0.0,再赋给变量 y。

    对于这种问题,其中一种解决方案是将右边的表达式变成浮点表达式,如下所

    示,这样变量 y 将像预期的那样被设置为 0.333333:

    double y = 1.0 3.0; 正确

    作为一种编程风格,在任何情况下都应将浮点值赋给浮点变量。编译器并没有

    要求必须这样做,但如果不这样做的话,不知什么时候一个简单的错误就可能阴魂

    不散,给你带来麻烦。2.7 舍入误差

    大多数浮点数只能大致正确地表示。有些数字,如果不是特别大的整数,可以

    准确地表示。但循环小数(如 13)和无理数(如 π)不能准确地表示。为表示这

    些数字,计算机必须将其舍入到最接近的浮点数。

    所需数字和实际得到的浮点数之间的差称为舍入误差(rounding error)。例

    如,以下两条语句应该是等价的:

    System.out.println(0.1 10);

    System.out.println(0.1 + 0.1 + 0.1 + 0.1 + 0.1

    + 0.1 + 0.1 + 0.1 + 0.1 + 0.1);

    但在很多计算机上,它们的输出如下:

    1.0

    0.9999999999999999

    原因是 0.1 在十进制中为有限小数,但在二进制中为循环小数,因此其浮点数

    表示只是近似的。将近似值相加会逐渐累积舍入误差。

    在很多应用领域,如计算机图形学、加密、统计分析和多媒体渲染,使用浮点

    数算术利大于弊。但如果要求绝对精确,应使用整数。例如,查看余额为 123.45

    美元的银行账户:

    double balance = 123.45; 可能存在舍入误差

    在这个示例中,随着不断地对变量执行算术运算(如存款和取款),它存储的

    值将不再准确。这可能会激怒顾客,甚至引发诉讼。为避免这种问题,可以用整数

    来表示余额:

    int balance = 12345; 美分数只要美分数不超过变量 int 可表示的最大值(约 20 亿),就可以使用这种

    解决方案。

    2.8 字符串运算符

    一般而言,不能对字符串执行数学运算,即便对那些看起来像数字的字符串亦

    是如此。下面的表达式是非法的:

    Hello - 1 World 123 Hello World

    运算符 + 可用于字符串,但其所作所为可能出乎意料。用于字符串时,运算符

    + 执行串接(concatenation),即首尾相连,因此 Hello, + World! 的

    结果为字符串 Hello, World!。

    再举一个例子。如果你声明了类型为 String 的变量 name,则表达式

    Hello, + name 会将变量 name 的值附加在字符串 hello 的后面,从而生成

    个性化的问候。

    鉴于对数字和字符串都定义了加法运算,因此 Java 可能执行意料之外的自动

    转换:

    System.out.println(1 + 2 + Hello);

    输出为3Hello

    System.out.println(Hello + 1 + 2);

    输出为Hello12

    Java 按从左到右的顺序执行这些运算。在第 1 行中,1 + 2 等于 3,而 3

    + Hello 的结果为 3Hello;在第 2 行中,Hello + 1 的结果为

    Hello1,而 Hello1 + 2 的结果为 Hello12。

    表达式包含多个运算符时,将根据运算顺序(order of operation)计算表

    达式。一般而言,Java 按从左到右的顺序执行运算(如 2.7 节所示),但对于数值运算符,Java 遵循如下的数学规则。

    乘除运算的优先级高于加减运算,这意味着先乘除后加减。因此 1 + 2

    3 的结果为 7,而不是 9,而 2 + 4 2 的结果为 4,而不是 3。

    运算符的优先级相同时,按从左到右的顺序执行。因此,表达式 minute

    100 60 先执行乘法运算;如果 minute 的值为 59,这个表达式将变为

    5900 60,结果为 98。如果按从右到左的顺序执行这些运算,将得到错误的

    结果 59 1。

    要想改变默认的运算优先级或对默认的运算优先级不太确定时,可使用括

    号。首先计算括号内的表达式,因此 (1 + 2) 3 的结果为 9。还可用括号

    让表达式更容易理解,如 (minute 100) 60,虽然就这个表达式而言,用不用括号对结果并没有影响。

    别费劲地去记运算符的优先级,尤其是除算术运算符外的其他运算符。如果表

    达式的含义不那么明显,可用括号让它清晰起来。

    2.9 组合

    前面分别介绍了编程语言的一些元素——变量、表达式和语句,但没有讨论如

    何结合使用它们。

    编程语言最有用的功能之一是能够组合(compose)小型构件。例如,在知道

    如何将数字相乘以及如何显示值后,我们可以将这些操作放在一条语句中:

    System.out.println(17 3);

    任何算术表达式都可用于打印语句中,我们见过这样的例子:

    System.out.println(hour 60 + minute);

    还可将表达式放在赋值语句的右边:int percentage;

    percentage = (minute 100) 60;

    赋值语句的左边必须是变量名,不能是表达式,这是因为赋值语句的左边要指

    定将结果放在什么地方,而表达式表示的并非存储位置。

    hour = minute + 1; 正确

    minute + 1 = hour; 导致编译错误

    就目前而言,能够将操作组合起来好像没什么大不了的,但在本书的后文中你

    将了解到,这让我们能够编写简洁的代码以执行复杂的计算。不过,也别忘乎所

    以,冗长而复杂的表达式可能会难以理解和调试。

    2.10 错误类型

    程序中可能出现的错误有三种:编译时错误、运行时错误和逻辑错误。区分这

    些错误可以更快地找出错误。

    编译时错误(compile-time error)指的是因违反 Java 语法(syntax)规

    则而导致的错误。例如,括号和大括号必须成对出现,所以 (1 + 2) 是合法的,而 8) 是非法的。8) 导致程序无法编译,而编译器将显示一条错误消息。

    编译器显示的错误消息通常会指出错误出现在程序的什么地方,有时还可以准

    确地指出错误。我们来重温一下第 1 章中的 Hello World 程序。

    public class Hello {

    public static void main(String[] args) {

    生成一些简单的输出

    System.out.println(Hello, World!);

    }

    }

    如果遗漏了打印语句末尾的分号,将出现类似于以下的错误消息:File: Hello.java [line: 5]

    Error: ';' expected

    真是太好了:这条错误消息准确地指出了错误的位置,还指出了是什么样的错

    误。

    然而,并非所有的错误消息都是容易理解的。有时编译器报告的错误位置不准

    确;有时对错误的描述模棱两可,几乎没什么帮助。

    例如,如果遗漏了方法 main 末尾(第 6 行)的右大括号,可能出现类似于

    以下的错误消息:

    File: Hello.java [line: 7]

    Error: reached end of file while parsing

    这里有两个问题。首先,这条错误消息是从编译器的角度而不是你的角度生成

    的。分析(parsing)指的是在转换前读取程序的过程;如果编译器到达文件末尾

    后分析还在进行的话,那么就意味着程序遗漏了什么东西,但编译器不知道遗漏了

    什么,也不知道在何处遗漏的。它认为错误发生在程序末尾(第 7 行),但遗漏的

    大括号应该在前一行。

    错误消息提供了很有用的信息,你应尽力阅读并理解它们,但也不能将它们奉

    为圭臬。

    刚从事编程的几周内,你可能会为找出编译错误花费大量的时间,但随着经验

    越来越丰富,你犯的错误将越来越少,找出错误的速度也将越来越快。

    第二种错误是运行时错误(run-time error),因其要等到程序运行后才会出

    现而得名。在 Java 中,这种错误发生在解释器执行字节码期间,也被称为异常,因为它们通常表明出现了异常而糟糕的情况。

    本书前几章的简单程序中很少出现运行时错误,因此可能需要过段时间才能见

    到它们。运行时错误发生时,解释器将显示一条错误消息,指出在什么地方出现了

    什么问题。例如,如果你不小心将零用作了除数,将出现类似于以下的错误消息:

    Exception in thread main java.lang.ArithmeticException: by zero at Hello.main(Hello.java:5)

    上述的输出对调试很有帮助。第 1 行指出了异常的名称

    ——java.lang.ArithmeticException,还具体地指出了发生的情况—— by

    zero(除以零)。接下来的一行指出了问题所在的方法;Hello.main 指的是

    Hello 类的方法 main;还指出了这个方法是在哪个文件(Hello.java)中定义的

    以及问题出现在第几行(5)。

    有些错误消息还包含无意义的信息,因此你面临的挑战之一是确定有用的部

    分,而不被多余的信息搞得不知所措。另外别忘了,导致程序崩溃的代码行可能并

    不是需要修改的代码行。

    第三种错误是逻辑错误(logic error)。存在逻辑错误的程序能够通过编

    译,且运行时不会出现错误消息,但不会做正确的事。相反,你让它怎么做,它就

    怎么做。例如,下面这个版本的 Hello World 程序存在一个逻辑错误:

    public class Hello {

    public static void main(String[] args) {

    System.out.println(Hello, );

    System.out.println(World!);

    }

    }

    这个程序能够通过编译并运行,但输出如下:

    Hello,World!

    如果我们要在一行中显示全部输出,那么上述输出就不正确。问题出在第 1

    行,它用的是 println,而我们原本想用的是 print(参见 1.5 节中的

    goodbye world 示例)。有时很难找出逻辑错误,因为你必须进行反向推导:根据输出结果推断程序行

    为不正确的原因,并确定如何让它的行为正确无误。编译器和解释器在这方面帮不

    了你,因为它们并不知道正确的行为是什么样的。

    了解这三种错误后,你应该阅读一下附录 C,其中搜集了一些我们最喜欢的调

    试建议。因为这些建议涉及了一些还未讨论的语言功能,所以你可能需要时不时地

    再次阅读这个附录。

    2.11 术语表

    变量

    命名的存储位置。所有变量都有类型,这是在创建变量时声明的。

    值

    数字、字符串或其他可存储在变量中的数据。每个值都属于特定的类型,如 int 或 String。

    声明

    创建变量并指定其类型的语句。

    类型

    从数学角度来说,类型是一个值集。变量的类型决定了它可存储哪些值。

    关键词

    编译器用来分析程序的保留词。关键词(如 public、class 和 void)

    不能用作变量名。

    赋值

    给变量指定值的语句。初始化

    首次给变量赋值。

    状态

    程序中的变量及其当前值。

    状态图

    程序在特定时点的状态的图形表示。

    运算符

    表示计算(如加、乘和字符串串接)的符号。

    操作数

    运算符操作的值。在 Java 中,大多数运算符需要两个操作数。

    表达式

    表示单个值的变量、运算符和值的组合。表达式也有类型,这是由表达式

    包含的运算符和操作数决定的。

    浮点

    一种数据类型,表示包含整数部分和小数部分的数字。在 Java 中,默认

    的浮点类型为 double。

    舍入误差

    要表示的数字和与之最接近的浮点数之间的差。

    拼接

    将两个值(通常是字符串)首尾相连。运算顺序

    决定运算顺序执行的规则。

    组合

    将简单的表达式和语句合并为复合的表达式和语句。

    语法

    程序的结构,即程序包含的单词和符号的排列方式。

    编译时错误

    导致源代码无法编译的错误,也叫“语法错误”。

    分析

    分析程序的结构,这是编译器做的第一项工作。

    运行时错误

    导致程序无法完成运行的错误,也叫“异常”。

    逻辑错误

    导致程序的行为不符合程序员预期的错误。

    2.12 练习

    本章的示例代码位于仓库 ThinkJavaCode 的目录 ch02 中,有关如何下载这

    个仓库,请参阅前言中的“使用示例代码”一节。做以下的练习前,建议你先编译

    并运行本章的示例。

    如果你还没有阅读 A.2 节,那么现在正是阅读的好时机。该节介绍了 DrJava

    的 Interactions 窗格,它提供了极佳的途径,让你无需编写完整的类定义就能开

    发并测试简短的代码片段。练习2-1

    如果你将本书用作教材的话,可能会很喜欢这个练习。找个同伴一起来玩

    Stump the Chump 的游戏吧。

    先编写一个能够通过编译并正确运行的程序。一个人在程序中添加一个错误,另一个人不能偷看,然后尝试找出并修复这个错误。在不编译程序的情况下找出错

    误得两分,求助于编译器找出错误得 1 分,找不出错误对手得 1 分。

    练习2-2

    这个练习旨在:用字符串拼接显示不同类型(int 和 String)的值;以每次

    添加几条语句的方式循序渐进地练习程序开发。

    (1) 新建一个程序,将其命名为 Date.java。输入或复制类似于程序 Hello

    World 中的代码,并确保程序能够通过编译并运行。

    (2) 仿照 2.4 节中的示例,编写一个创建变量 day、date、month 和 year

    的程序。变量 day 用于存储星期几(如星期五),date 用于存储日期(如 13

    号)。这些变量应声明为何种类型呢?将表示当前日期的值赋给这些变量。

    (3) 显示(打印)每个变量的值,且每个变量要独占一行。这是一个中间步

    骤,有助于确认到目前为止一切正确。编译并运行这个程序,然后再接着往下做。

    (4) 修改程序,使其以美国标准格式显示日期,如 Thursday, July 16,2015。

    (5) 修改程序,使其以欧洲格式显示日期。最终的输出应类似于以下这样:

    American format:

    Thursday, July 16, 2015

    European format:

    Thursday 16 July 2015

    练习2-3这个练习旨在:使用一些算术运算符;考虑用多个值表示复合实体(如时

    间)。

    (1) 新建一个程序,将其命名为 Time.java。从现在开始,我们将不再提醒你

    先编写一个可运行的小程序,但你应该这样做。

    (2) 仿照 2.4 节中的示例,创建变量 hour、minute 和 second,并将大致

    表示当前时间的值赋给这些变量。请使用 24 小时制,即如果当前时间是下午两

    点,就将变量 hour 的值设置为 14。

    (3) 让程序计算并显示从午夜开始过去了多少秒。

    (4) 计算并显示当天还余下多少秒。

    (5) 计算并显示当天已逝去时间的百分比。如果用整数计算百分比,可能会出

    现问题,因此请考虑使用浮点数。

    (6) 根据当前时间修改变量 hour、minute 和 second 的值,再编写代码来

    计算从你开始做这个练习算起,已过去了多少时间。

    提示:你可能想在计算期间用额外的变量来存储值。只用于计算而不显示的变

    量被称为“中间变量”或“临时变量”。第 3 章 输入和输出

    本书前面介绍的程序都只显示消息,并不涉及太多的计算。本章将介绍如何从

    键盘读取输入,并用这些输入来计算结果,再设置结果的输出格式。

    3.1 System类

    本书前面一直在使用 System.out.println,但你可能没有想过其含

    义。System 是一个类,提供了与运行程序的系统或环境相关的方法,以及特殊值

    System.out,这个值提供了显示输出的方法,其中包括 println。

    实际上,我们可用 System.out.println 来显示 System.out 的值:

    System.out.println(System.out);

    结果如下:

    java.io.PrintStream@685d72cd

    上述输出表明,System.out 是一个 PrintStream,而 PrintStream 是在

    java.io 包中定义的。包(package)是一组相关的类;java.io 包含用于

    IO(输入和输出)的类。

    @ 后面的数字和字母是 System.out 的十六进制地址(address)。值的地址

    指的是值在计算机内存中的位置,可能随计算机而异。在这个示例中,地址为

    685d72cd,但如果你运行这些代码,可能会得到不同的地址。

    如图 3-1 所示,System 是在文件 System.java 中定义的,而

    PrintStream 是在文件 PrintStream.java 中定义的。这些文件都包含在 Java

    库(library)中,Java 库是一个庞大的类集,你可以在程序中使用其中的任何类。

    图 3-1:System.out.println 指向 System 类的 out 变量,这个变量是

    一个提供了方法 println 的 PrintStream

    3.2 Scanner类

    System 类还提供了特殊值 System.in,这是一个 InputStream,提供了从

    键盘读取输入的方法。这些方法用起来并不那么容易,好在 Java 还提供了其他

    类,从而能更容易地处理常见的输入任务。

    例如,Scanner 类提供了输入单词、数字和其他数据的方法,其包含在

    java.util 包中。java.util 包含的类很有用,因此被称为“实用类”。在使用

    Scanner 之前,必须先像下面这样导入它:

    import java.util.Scanner;

    这条导入语句(import statement)告诉编译器,当说到 Scanner 时,你

    指的是 java.util 中定义的 Scanner。必须将这一点传达给编译器,因为其他包中可能也存在 Scanner 类。使用导入语句可避免代码存在二义性。

    导入语句不能存在于类定义中。根据约定,它们通常位于文件的开头。

    接下来,你需要创建一个 Scanner 对象:

    Scanner in = new Scanner(System.in);

    这行代码声明了一个名为 in 的 Scanner 变量,并新建了一个 Scanner 对

    象以便从 System.in 获取输入。

    Scanner 提供了方法 nextLine,这个方法从键盘读取一行输入,并返回一个

    String。下面的示例读取了两行,并向用户显示了它们:

    import java.util.Scanner;

    public class Echo {

    public static void main(String[] args) {

    String line;

    Scanner in = new Scanner(System.in);

    System.out.print(Type something: );

    line = in.nextLine;

    System.out.println(You said: + line);

    System.out.print(Type something else: );

    line = in.nextLine;

    System.out.println(You also said: + line);

    }

    }

    如果你在没有包含上述导入语句的情况下引用 Scanner,编译器将显示一条类

    似于 cannot find symbol(找不到符号)的消息,这意味着编译器不知道你说的

    Scanner 指的是什么。

    你可能会心存疑惑,为何不用导入 System 就能使用它呢? System 位于可自动导入的 java.lang 包中。Java 文档指出,java.lang 提供了 Java 编程语言

    中的基本类。String 类也位于 java.lang 包中。

    3.3 程序结构

    至此,我们介绍了 Java 程序的所有组成元素,图 3-2 显示了这些组织单

    元。

    图 3-2:Java 语言的组成元素,按从大到小的顺序排列

    我们来复习一下。包是类的集合,而类定义了方法;方法包含语句,而有些语

    句包含表达式;表达式由标记(token)组成,而标记是程序的基本元素,其中包括

    数字、变量名、运算符、关键词以及括号、大括号和分号等标点。

    Java 的标准版自带了可在程序中导入的数千个类,这令人既激动又惊恐。要

    浏览这个库,可访问 http:docs.oracle.comjavase8docsapi。Java

    库主要是用 Java 编写的。

    请注意,Java 语言和 Java 库的主要差别在于,前者定义了图 3-2 所示元素的语法和含义,而后者提供了内置类。

    3.4 英寸到厘米的转换

    现在让我们来看一个有点实用价值的示例。虽然全球的大部分地区都在用公制

    度量衡,但有些国家依然还在用英制单位。例如,与欧洲的朋友谈论天气时,美国

    人可能要在摄氏温度和华氏温度之间进行转换。另外,还可能要将身高从英寸数转

    换为厘米数。

    我们可以编写一个程序来提供帮助。在这个程序中,我们将用一个 Scanner

    对象来获取以英寸为单位的值,然后将其转换为厘米数并显示结果。下面的代码行

    声明了所需要的变量并创建了一个 Scanner 对象:

    int inch;

    double cm;

    Scanner in = new Scanner(System.in);

    接下来需要提示用户输入值。为此,我们将使用 print 而不是 println,让

    用户能够在提示所在的行进行输入。另外,我们还将使用 Scanner 类的方法

    nextInt,以便从键盘读取输入并将其转换为整数:

    System.out.print(How many inches? );

    inch = in.nextInt;

    接下来,我们将英寸数乘以 2.54(因为 1 英寸相当于 2.54 厘米)并显示结

    果:

    cm = inch 2.54;

    System.out.print(inch + in = );

    System.out.println(cm + cm);

    这些代码可以正确运行,但存在一个小问题:其他程序员看到这些代码时,可

    能不明白 2.54 是怎么来的。为方便其他程序员(还有未来的你),更好的做法是将这个值赋给一个变量,并给这个变量指定一个有意义的名称。我们将在 3.5 节中

    对此进行演示。

    3.5 字面量和常量

    在程序中,2.54(或 in =)这样的值被称为字面量(literal)。一般而

    言,使用字面量没什么错,但如果在表达式中使用 2.54 这样的数字,却不作任何

    解释的话,代码将难以理解。另外,如果同样的值出现多次,且以后可能需要修

    改,那么代码将难以维护。

    这样的值有时被称为魔幻数字(magic number,这里的“魔幻”可不是褒义

    的),一种很好的做法是像以下这样将魔幻数字赋给变量,并给变量指定有意义的

    名称:

    double cmPerInch = 2.54;

    cm = inch cmPerInch;

    这个版本更容易理解,而且不那么容易出错,但还是存在一个问题,那就是变

    量是可变的,而 1 英寸对应的厘米数是不变的。一旦给 cmPerInch 赋值,就再也

    不应该修改。Java 提供了实施这种规则的语言特性——关键词 final。

    final double CM_PER_INCH = 2.54;

    将变量声明为 final 意味着对其进行初始化后,就不能重新赋值了。如果你

    试图这样做,编译器就会报错。声明为 final 的变量被称为常量(constant);

    根据约定,常量名全部大写,且单词间用下划线(_)连接。

    3.6 设置输出的格式

    使用 print 或 println 输出 double 值时,最多显示 16 位小数:

    System.out.print(4.0 3.0);结果如下:

    1.3333333333333333

    这可能比你想要的要多。System.out 提供了另一个方法 printf,让你对输

    出格式有更大的控制权,printf 中的 f 指的是“格式化”。以下是一个示例:

    System.out.printf(Four thirds = %.3f, 4.0 3.0);

    括号中的第一个值为格式字符串(format string),指定了输出的显示方

    式。上述的格式字符串中包含普通文本,普通文本的后面是格式说明符(format

    specifier)——以百分号打头的特殊序列。格式说明符 %.3f 指定接下来的值应

    显示为浮点数,并舍入到三位小数。上述代码的结果如下:

    Four thirds = 1.333

    格式字符串可包含任意数目的格式说明符,下面的格式字符串就包含了两个格

    式说明符:

    int inch = 100;

    double cm = inch CM_PER_INCH;

    System.out.printf(%d in = %f cm\n, inch, cm);

    结果如下:

    100 in = 254.000000 cm

    与 print 一样,printf 也不在末尾换行;所以格式字符串通常以换行符结

    尾。

    格式说明符 %d 用于显示整数值,其中的 d 表示 decimal(十进制)。值依次与格式说明符配对,因此,用于 inch 的格式说明符为 %d,用于 cm 的格式说

    明符为 %f。

    学习格式字符串相当于学习 Java 中的一种子语言;涉及的选项很多,细节可

    能让人不可重负。表 3-1 列出了一些常用的格式说明符,旨在让你大致地了解其中

    的工作原理;更多细节请参阅 java.util.Formatter 的相关文档。要想找到有关

    Java 类的文档,最简单的方法是在网上搜索 Java 和类名。

    表3-1:格式说明符示例

    %d 十进制整数 12345

    %08d 添加前导零,确保显示的值至少包含 8 位 00012345

    %f 浮点数 6.789000

    %.2f 舍入到两位小数 6.79

    3.7 厘米到英寸的转换

    现在假设有一个以厘米为单位的值,我们想将其转换为与之最接近的英寸数。

    你可能很想这样编写代码:

    inch = cm CM_PER_INCH; 语法错误

    但这将导致编译错误,会出现类似于“Bad types in assignment: from

    double to int”这样的错误消息。这是因为右边是浮点数,而左边是整数变量。

    要将浮点值转换为整数,最简单的方式是使用类型转换(type cast),类型

    转换因将值从一种类型塑造或铸造成另一种类型而得名。类型转换的语法是将类型

    名放在括号内,并将其用作运算符。

    double pi = 3.14159;

    int x = (int) pi;

    运算符 (int) 会将它后面的值转换为整数。在这个示例中,x 将被设置为3。与整数除法一样,转换为整数时总是向下圆整,即便小数部分为 0.999999(或

    -0.999999)也是如此。换言之,将直接丢弃小数部分。

    类型转换的优先级高于算术运算。在下面的示例中,先将 pi 的值转换为整

    数,然后再执行乘法运算,因此结果为 60.0,而不是 62.0。

    double pi = 3.14159;

    double x = (int) pi 20.0;

    请务必牢记这一点。下面的代码演示了如何将厘米数转换为英寸数:

    inch = (int) (cm CM_PER_INCH);

    System.out.printf(%f cm = %d in\n, cent, inch);

    转换运算符后面的括号使得除法运算先执行,然后再进行类型转换。因此除法

    运算的结果将向下圆整;我们将在下一章介绍如何将浮点数圆整为与之最接近的整

    数。

    3.8 求模运算符

    现在我们再进一步:假设你有一个以英寸为单位的值,并且想将其转换为英尺

    数和英寸数。为此,需要除以 12(1 英尺对应 12 英寸),并将余数记录下来。

    本书前面已经介绍过除法运算符(),用于计算两个数的商。如果两个操作数

    都为整数,那么它将执行整数除法。Java 还提供了求模(modulus)运算符

    (%),用于计算两个数相除的余数。

    可像以下这样用除法和求模运算来将英寸数转换为英尺数和英寸数:

    quotient = 76 12; 除法

    remainder = 76 % 12; 求模

    第 1 行的结果为 6,第 2 行读作“76 与 12 的模”,结果为 4。因此,76英寸相当于 6 英尺 4 英寸。

    求模运算符看起来像百分号,但其实可以将其视为除号(÷)向左旋转 90 度

    的结果。

    求模运算符很有用,例如,可以检查一个数能否被另一个数整除:如果 x % y

    的结果为零,就说明 x 能够被 y 整除。可用求模运算来提取数字中的某些位:x

    % 10 的结果为 x 的个位,而 x % 100 的结果为最后两位。另外,很多加密算法

    都大量地使用了求模运算符。

    3.9 整合

    至此,你应该对 Java 有足够的了解,能够编写解决日常问题的程序了。你知

    道如何导入 Java 库中的类、如何创建 Scanner 对象、如何从键盘获取输入、如

    何用 printf 设置输出的格式以及如何执行整数除法和求模运算。现在让我们结合

    这些知识,编写一个完整的程序:

    import java.util.Scanner;

    将厘米数转换为英尺数和英寸数

    public class Convert {

    public static void main(String[] args) {

    double cm;

    int feet, inches, remainder;

    final double CM_PER_INCH = 2.54;

    final int IN_PER_FOOT = 12;

    Scanner in = new Scanner(System.in);

    提示用户输入值并读取这个值

    System.out.print(Exactly how many cm? );

    cm = in.nextDouble;

    转换并输出结果

    inches = (int) (cm CM_PER_INCH);

    feet = inches IN_PER_FOOT; remainder = inches % IN_PER_FOOT;

    System.out.printf(%.2f cm = %d ft, %d in\n,cm, feet, remainder);

    }

    }

    虽然没有要求,但所有的变量和常量都是在 main 方法的开头声明的。这种做

    法让人更容易获悉这些变量和常量的类型以及算法涉及了哪些数据。

    为提高可读性,可用一个空行将算法的主要步骤分隔开,且每个主要步骤都以

    注释打头。这个程序还包含文档注释(),我们将在下一章中更详细地介绍。

    很多算法(包括程序 Convert)都结合使用了除法和求模运算。在程序

    Convert 中,这两种运算使用的除数相同,都是 IN_PER_FOOT。

    常见的编程风格是将过长(超过 80 个字符)的语句分成多行,以便阅读时不

    用水平滚动。

    3.10 Scanner类的bug

    在有了一些使用 Scanner 的经验后,有必要提醒你一下,它存在一个出人意

    料的行为。下面的代码片段让用户输入姓名和年龄:

    System.out.print(What is your name? );

    name = in.nextLine;

    System.out.print(What is your age? );

    age = in.nextInt;

    System.out.printf(Hello %s, age %d\n, name, age);

    其输出可能类似于以下这样:

    Hello Grace Hopper, age 45

    先读取 String 再读取 int 时,一切都正常,但如果先读取 int 再读取String,将发生怪异的事情。

    System.out.print(What is your age? );

    age = in.nextInt;

    System.out.print(What is your name? );

    name = in.nextLine;

    System.out.printf(Hello %s, age %d\n, name, age);

    如果尝试运行上述示例代码,你将发现它根本不允许输入姓名,而在你输入年

    龄后会直接显示输出:

    What is your name? Hello , age 45

    要想明白其中的原因,你必须知道的是,Scanner 并不像我们那样将输入视为

    多行;相反,它获得的是一个如图 3-3 所示的“字符流”。

    图 3-3:Scanner 眼中的字符流

    其中的箭头指向 Scanner 将读取的下一个字符。调用 nextInt 时,它会不

    断读取字符,直到遇到非数字字符。图 3-4 显示了调用 nextInt 后流的状态。

    图 3-4:调用 nextInt 后的字符流

    此时,nextInt 返回 45。接下来,程序显示提示 What is your name?,并

    调用 nextLine;这个方法会不断读取字符,直到遇到换行符。然而,由于下一个

    字符就是换行符,因此 nextLine 返回一个空字符串。为解决这个问题,需要在 nextInt 后面多调用一次 nextLine。

    System.out.print(What is your age? );

    age = in.nextInt;

    in.nextLine; 读取换行符

    System.out.print(What is your name? );

    name = in.nextLine;

    System.out.printf(Hello %s, age %d\n, name, age);

    读取独占一行的 int 或 double 值时,经常需要使用技巧:先读取数字,再

    读取当前行余下的全部内容(仅仅是一个换行符)。

    3.11 术语表

    包

    一组彼此相关的类。

    地址

    值在计算机内存中的位置,通常用十六进制整数表示。

    库

    可在其他程序中使用的一系列包和类。

    导入语句

    让程序能够使用其他包中定义的类的语句。

    标记

    程序的基本元素,如单词、空格、符号或数字。

    字面量

    在源代码中出现的值,例如,Hello 是一个字符串字面量,而 74 是一个整数字面量。

    魔幻数字

    表达式中没有任何解释的数字,通常应将其替换为常量。

    常量

    声明为 final 的变量,其值不可修改。

    格式字符串

    传递给 printf 以便指定输出格式的字符串。

    格式说明符

    以百分号开头的特殊编码,指定了相应值的数据类型和格式。

    类型转换

    从一种类型明确地转换为另一种类型的操作。在 Java 中表现为用括号括

    起的类型名,如(int)。

    求模

    一种运算符,用于计算两个整数相除的余数。在 Java 中用百分号表示。

    例如,5 \% 2 的结果为 1。

    3.12 练习

    本章的示例代码位于仓库 ThinkJavaCode 的目录 ch03 中,有关如何下载这

    个仓库,请参阅前言中的“使用示例代码”一节。做以下的练习前,建议你先编译

    并运行本章的示例。

    如果你还没有阅读 A.3 节,那么现在正是阅读的好时机。该节介绍了命令行界

    面,这是与计算机交互的强大而高效的一种方式。练习3-1

    使用 printf 时,Java 编译器不会检查其中的格式字符串。请尝试用 %f 显

    示一个类型为 int 的值,并看看结果将会如何。用 %d 显示 double 值呢?指定

    两个格式说明符,却只提供一个值,又会发生什么呢?

    练习3-2

    编写一个程序,将摄氏温度转换为华氏温度。这个程序应:(1) 提示用户输入

    摄氏温度值; (2) 从键盘读取一个 double 值;(3) 计算结果;(4) 将输出设置

    为包含一位小数。例如,它应显示 24.0 C=75.2 F 这样的输出。

    下面是转换公式,请注意,千万不要用整数除法!

    练习3-3

    编写一个程序,将秒数转换为小时数、分钟数和秒数。这个程序应:(1) 提示

    用户输入秒数;(2) 从键盘读取一个整数;(3) 计算结果;(4) 用 printf 显示

    输出。例如,它应显示 5000 seconds = 1 hours, 23 minutes, and 20

    seconds 这样的输出。

    提示:可使用求模运算符。

    练习3-4

    这个练习的目标是编写一个“猜数”游戏,其输出应类似于以下这样:

    I'm thinking of a number between 1 and 100

    (including both). Can you guess what it is?

    Type a number: 45

    Your guess is: 45

    The number I was thinking of is: 14

    You were off by: 31要想生成随机数,可使用 java.util 中的 Random 类,其工作原理如下:

    import java.util.Random;

    public class GuessStarter {

    public static void main(String[] args) {

    生成一个随机数

    Random random = new Random;

    int number = random.nextInt(100) + 1;

    System.out.println(number);

    }

    }

    与本章介绍的 Scanner 类一样,要想使用 Random 类,必须先导入。另外,与创建 Scanner 对象一样,必须用 new 运算符创建一个 Random 对象(随机数

    生成器)。

    然后就可以用方法 nextInt 来生成随机数了。在上面的示例

    中,nextInt(100) 的结果是一个 0~99(闭区间)的数字。通过将这个结果加

    1,将得到一个 1~100(闭区间)的数字。

    (1) GuessStarter 的定义位于文件 GuessStarter.java 中,而这个文件位

    于本书代码仓库的目录 ch03 中。

    (2) 编译并运行这个程序。

    (3) 修改这个程序,提示用户输入一个数字,再用 Scanner 读取一行用户输

    入。编译并测试这个程序。

    (4) 将用户输入作为整数读取,并显示结果。再次编译并测试这个程序。

    (5) 计算并显示用户猜测的数字和生成的随机数之间的差。第 4 章 void 方法

    到目前为止,我们编写的程序都很短,只包含一个类和一个方法(main)。我

    们将在本章演示如何将较长的程序组织成多个方法和类,还将介绍 Math 类,它提

    供了执行常见数学运算的方法。

    4.1 Math类的方法

    学习数学时,你可能见过 sin 和 log 这样的函数,还学习过如何计算像

    sin(π2) 和 log(1x) 这样的表达式的值:先计算括号内的表达式,它们被称为

    函数的实参(argument),然后计算函数本身,可能还要用到计算器。

    计算 log(1sin(π2)) 这样更复杂的表达式时,可重复执行这个过程:先计

    算最里面的函数的实参,再计算函数本身,依此类推。

    Java 库包含一个 Math 类,它提供了执行常见数学运算的方法。这个类位于

    java.lang 包中,因此无需导入。可像下面这样使用或调用(invoke)Math 类的

    方法:

    double root = Math.sqrt(17.0);

    double angle = 1.5;

    double height = Math.sin(angle);

    第 1 行将 root 设置为 17 的平方根;第 3 行计算 1.5(变量 angle 的

    值)的正弦。

    三角函数 sin、cos 和 tan 的实参应以弧度为单位。要想将度数转换为弧度

    数,可将其除以 180 再乘以 π。好在 Math 类提供了一个名为 PI 的 double

    常量,它包含 π 的近似值:

    double degrees = 90;

    double angle = degrees 180.0 Math.PI;请注意,常量名 PI 的字母全都是大写;Java 根本不知道 Pi、pi 和 pie

    为何物。另外,PI 是一个变量而不是方法的名称,因此它后面不带括号。常量

    Math.E 亦是如此,它包含欧拉数的近似值。

    在度数和弧度数之间进行转换是一种常见的运算,因此 Math 类提供了执行这

    种运算的方法。

    double radians = Math.toRadians(180.0);

    double degrees = Math.toDegrees(Math.PI);

    另一个很有用的方法是 round,它将浮点数圆整为最接近的整数,并将其作为

    long 值返回。long 类似于 int,但可表示的值更大。更具体地说,int 长 32

    位,可存储的最大值为 231 - 1——约为 20 亿;long 长 64 位,可存储的最大

    值为 263 - 1——约为 9×1018。

    long x = Math.round(Math.PI 20.0);

    结果为 63(这是将 62.8319 向上圆整得到的)。

    请花点时间阅读 Math 类的这些方法和其他方法的文档。要想查找有关 Java

    类的文档,最简单的方法是在网上搜索 Java 和类名。

    4.2 再谈组合

    与数学函数一样,Java 方法也是可以组合的,这意味着可在一个表达式中包含

    另一个表达式。例如,可将任何表达式用作方法的实参:

    double x = Math.cos(angle + Math.PI 2.0);

    这条语句将 Math.PI 除以 2,再将结果与 angle 相加,然后计算得到的和的余弦。还可将一个方法的结果用作另一个方法的实参:

    double x = Math.exp(Math.log(10.0));

    在 Java 中,方法 log 总是以 e 为底,因此这条语句计算以 e 为底的 10

    的对数,再将结果作为指数计算 e 的相应次幂,然后将得到的结果赋给变量 x。

    Math 类的有些方法接受多个实参,例如,Math.pow 接受两个实参,并计算第

    一个实参的第二个实参次幂。下面的这行代码将值 1024.0 赋给变量 x:

    double x = Math.pow(2.0, 10.0);

    在用 Math 类的方法时,遗漏 Math 是一种常见的错误。例如,如果试图调用

    pow(2.0, 10.0),将出现类似于以下的错误消息:

    File: Test.java [line: 5]

    Error: cannot find symbol

    symbol: method pow(double,double)

    location: class Test

    消息 cannot find symbol 令人迷惑,但最后一行提供了有用的线索:编译

    器试图在调用方法 pow 的类(Test)中查找它。如果没有指定类名,编译器将在

    当前类中查找。

    4.3 添加方法

    此时你应该已经猜到了,在一个类中可定义多个方法,如下例所示:

    public class NewLine {

    public static void newLine {

    System.out.println;

    } public static void main(String[] args) {

    System.out.println(First line.);

    newLine;

    System.out.println(Second line.);

    }

    }

    类名为 NewLine。根据约定,类名的首字母要大写。NewLine 包含两个方

    法:newLine 和 main。别忘了,Java 区分大小写,因此 NewLine 和 newLine

    不是一回事。

    方法名的首字母应小写,并采用“骆驼拼写法”,即类似于

    jammingWordsTogetherLikeThis 这样的格式。可给方法指定任何名称,但 main

    和其他 Java 关键词除外。

    newLine 和 main 是公有的,这意味着可在其他类中调用。它们都是静态的,那么静态是什么意思呢?现在暂时无法解释。另外,它们的返回类型都是 void,这

    意味着它们不会像 Math 类的方法那样返回结果。

    方法名后面的括号包含了一个变量列表,这些变量被称为形参

    (parameter),用于存储方法的实参。main 只有一个名为 args 的形参,类型

    为 String[],这意味着调用这个 main 方法时,必须提供一个字符串数组(将在

    本书的后面介绍)。

    由于 newLine 没有形参,所以调用时不需要提供实参,如在 main 方法中的

    调用所示。另外,由于 newLine 和 main 位于同一个类中,因此在 main 中调用

    newLine 时无需指定类名。

    这个程序的输出如下:

    First line.

    Second line.注意,输出行之间有空行。如果要想让输出行相距得更远,可多次调用方法

    newLine:

    public static void main(String[] args) {

    System.out.println(First line.);

    newLine;

    newLine;

    newLine;

    System.out.println(Second line.);

    }

    我们也可以再编写一个显示三个空行的方法:

    public static void threeLine {

    newLine;

    newLine;

    newLine;

    }

    public static void main(String[] args) {

    System.out.println(First line.);

    threeLine;

    System.out.println(Second line.);

    }

    可以多次调用同一个方法,还可以在一个方法中调用另一个方法。在这个示例

    中,方法 main 调用了方法 threeLine,而方法 threeLine 调用了 newLine。

    初学者常常对不厌其烦地创建新方法心存疑惑。这样做的原因很多,这个示例

    说明了其中的几个原因。

    通过创建新方法,你可以给一组语句指定名称,从而让代码更容易阅读和

    理解。

    引入新方法可消除重复的代码,从而缩小程序的规模。例如,要显示 9 个

    空行,可调用 threeLine 三次。一种常见的问题解决技巧是将任务划分成子问题。方法让你能够只专注于

    子问题,然后再将方法组合成完整的解决方案。

    4.4 执行流程

    将 4.3 节中的代码组合起来可以得到类似于下面这样的完整程序:

    public class NewLine {

    public static void newLine {

    System.out.println;

    }

    public static void threeLine {

    newLine;

    newLine;

    newLine;

    }

    public static void main(String[] args) {

    System.out.println(First line.);

    threeLine;

    System.out.println(Second line.);

    }

    }

    阅读包含多个方法的类定义时,你可能很想按照从头到尾的顺序阅读。但这样

    做很可能让你感到迷惑,因为程序的执行流程(flow of excution)并不是这样

    的。

    不管 main 方法位于源代码文件的什么地方,程序总是从这个方法的第一条语

    句开始执行。语句按顺序以每次一条的方式执行,直到遇到方法调用。我们可将方

    法调用视为改道:不是直接执行下一条语句,而是跳转到被调用的方法的第一行,执行完这个方法的全部语句后,再回到离开的地方继续执行。

    这好像很简单,但别忘了,一个方法可以调用另一个方法。执行 main 方法

    时,中途离开去执行 threeLine 的语句;执行 threeLine 时,又中途离开去执行 newLine;而 newLine 调用 println,导致再次改道。

    好在 Java 擅于跟踪当前的运行方法,因此,println 执行完毕后,它会回到

    离开 newLine 的地方;newLine 执行完毕后,回到离开 threeLine 的地方;而

    threeLine 执行完毕后,又回到离开 main 的地方。

    总之,阅读程序时,不要按照从头到尾的顺序阅读,而应按执行流程阅读。

    4.5 形参和实参

    前面使用的有些方法需要实参,实参是调用方法时提供的值。例如,要计算一

    个数字的正弦就必须提供这个数字,因此,sin 接受一个 double 实参。要显示一

    条消息就必须提供这条消息,因此,println 接受一个 String 实参。

    在使用方法时提供的是实参,在编写方法时指定的是形参。形参列表指定了必

    须提供哪些实参,以下是一个示例:

    public class PrintTwice {

    public static void printTwice(String s) {

    System.out.println(s);

    System.out.println(s);

    }

    public static void main(String[] args) {

    printTwice(Don't make me say this twice!);

    }

    }

    printTwice 包含了一个名为 s,类型为 String 的形参。调用 printTwice

    时,必须提供一个类型为 String 的实参。

    方法执行前,实参会赋给形参。在这个示例中,实参 Don't make me say

    this twice! 被赋给形参 s。

    这个过程被称为参数传递(parameter passing),因为值从方法外传到了方法内。实参可以是任何表达式,因此,如果声明了一个 String 变量,就可将其用

    作实参:

    String argument = Never say never.;

    printTwice(argument);

    用作实参的值必须与形参的类型相同,例如,如果你试图像下面这样做:

    printTwice(17); 语法错误

    将出现类似于下面这样的错误消息:

    File: Test.java [line: 10]

    Error: method printTwice in class Test cannot be applied

    to given types;

    required: java.lang.String

    found: int

    reason: actual argument int cannot be converted to

    java.lang.String by method invocation conversion

    在有些情况下,Java 能够自动将实参从一种类型转换为另一种类型。例

    如,Math.sqrt 需要接受一个 double 实参,但如果你调用了 Math.sqrt(25),整数值 25 将自动转换为浮点值 25.0。但就 printTwice 而言,Java 并不能

    (或不会)将提供给它的整数 17 转换为 String。

    形参和其他变量只存在于当前的方法中;在 main 中,没有 s 这样的变量,如果试图在这个方法中使用它,将导致编译错误。同样,printTwice 中没有 args

    变量,这个变量位于 main 中。

    鉴于变量只在定义它的方法中存在,因此它们常被称为局部变量(local

    varible)。

    4.6 多个形参下面是一个接受两个形参的方法:

    public static void printTime(int hour, int minute) {

    System.out.print(hour);

    System.out.print(:);

    System.out.println(minute);

    }

    你可能很想将上述的形参列表写成下面这样:

    public static void printTime(int hour, minute) {...

    但这种格式(省略第二个 int)只适用于变量声明;在形参列表中,必须分别

    指定每个变量的类型。

    要调用这个方法,必须提供两个整数实参:

    int hour = 11;

    int minute = 59;

    printTime(hour, minute);

    常见的错误是像下面这样声明实参的类型:

    int hour = 11;

    int minute = 59;

    printTime(int hour, int minute); 语法错误

    这是一种语法错误:在编译器看来,int hour 和 int minute 是变量声明,而不是表达式。如果将整数字面量用作实参,则不能声明它们的类型:

    printTime(int 11, int 59); 语法错误4.7 栈图

    将 4.6 节的代码片段组合起来可以得到完整的类定义,如下:

    public class PrintTime {

    public static void printTime(int hour, int minute) {

    System.out.print(hour);

    System.out.print(:);

    System.out.println(minute);

    }

    public static void main(String[] args) {

    int hour = 11;

    int minute = 59;

    printTime(hour, minute);

    }

    }

    printTime 有两个形参——hour 和 minute。main 有两个变量,也分别名

    为 hour 和 minute。虽然名称相同,但这些变量是不同的变量:printTime 中的

    hour 和 main 中的 hour 指向不同的存储位置,可以有不同的值。

    例如,你可以像下面这样调用 printTime:

    int hour = 11;

    int minute = 59;

    printTime(hour + 1, 0);

    调用这个方法前,Java 会先计算实参的值,这里分别为 12 和 0,然后将这

    些值赋给形参。在 printTime 中,hour 的值为 12,而不是 11,而 minute 的

    值为 0,而不是 59。另外,即便 printTime 修改了其形参的值,但不会影响

    main 中的变量。

    要想对程序的方方面面进行跟踪,一种方法是绘制栈图(stack diagram)。

    栈图是一种显示方法调用的状态图(参见 2.3 节)。对于每一个方法,栈图中都有一个名为栈帧(frame)的方框,其中包含了这个方法的形参和变量。方法名位于栈

    帧的外面,而变量和形参则位于栈帧内。

    与状态图一样,栈图也显示了变量和方法在特定时点的状态。图 4-1 是方法

    printTime 刚执行时的栈图。

    图 4-1:PrintTime 的栈图

    4.8 阅读文档

    Java 的优点之一是自带了庞大的类库和方法。但在使用之前,可能还必须阅读

    文档,而这并非总是那么容易。

    例如,咱们来看看 3.2 节中使用的 Scanner 类文档。要找到这个文档,可在

    网上搜索 Java Scanner,图 4-2 是该文档页面的屏幕截图。图 4-2:Scanner 文档的屏幕截图

    其他类的文档格式与此类似。第 1 行是类所属的包,如 java.util;第 2 行

    是类名。截屏中的 All Implemented Interfaces 列举了部分 Scanner 能够做

    的事情,这里不打算做更深入的介绍。

    文档的下一部分是描述,阐述了当前类的用途,还包含了使用示例。这些内容

    可能难以理解,因为其中使用了你还没有学过的术语,但其中的示例经常很有用。

    要开始使用新类,一种不错的方式是将文档中的示例粘贴到测试文件中,看看它们

    能否编译并运行。

    有个示例演示了如何用 Scanner 从 String 而不是 System.in 读取输入:

    String input = 1 fish 2 fish red fish blue fish;

    Scanner s = new Scanner(input);描述、代码示例和其他细节的后面是以下几个表格:

    Constructor summary

    创建或构造 Scanner 对象的方式

    Method summary

    Scanner 提供的方法列表

    Constructor detail

    更多与 Scanner 对象创建方式有关的信息

    Method detail

    有关各个方法的详细信息

    例如,下面是 nextInt 的摘要信息:

    public int nextInt

    Scans the next token of the input as an int.

    第 1 行是方法的特征标(signature),它指定了方法的名称、形参(无)和

    返回类型(int);第 2 行简单地描述了这个方法的功能。

    表格 Method detail 更详细地阐述了这个方法:

    public int nextInt

    Scans the next token of the input as an int.

    An invocation of this method of the form nextInt behaves in

    exactly the same way as the invocation nextInt(radix), where

    radix is the default radix of this scanner.

    Returns:

    the int scanned from the input

    Throws:InputMismatchException - if the next token does not match

    the Integer regular expression, or is out of range

    NoSuchElementException - if input is exhausted

    IllegalStateException - if this scanner is closed

    其中的 Returns 部分描述了这个方法成功时返回的结果,而 Throws 部分描

    述了可能发生的错误及其引发的异常。

    要想熟练地阅读文档并就哪些部分可以忽略作出准确的判断,可能需要一段时

    间的学习,但这样的付出是值得的。知道 Java 库有哪些类可避免重复劳动,只需

    阅读少量的文档就可避免繁重的调试工作。

    4.9 编写文档

    受益于优秀文档的同时,应该编写优秀的文档来作为回报。Java 语言提供了一

    项很好的功能,即可以在源代码中嵌入文档。这让你能够在编写代码的同时编写文

    档,同时,更容易在修改代码时确保文档与代码一致。

    可用工具 Javadoc 自动提取包含在源代码中的文档,并生成格式良好的

    HTML。这个工具包含在标准 Java 开发环境中,被大家广泛使用。事实上,Java

    库的在线文档就是用 Javadoc 生成的。

    Javadoc 扫描源代码文件以查找格式特殊的文档注释——也称为 Javadoc 注

    释。这种注释以 (两个星号)打头,并以 (一个星号)结尾,位于它们之

    间的所有内容都被视为文档。

    下面的类定义包含两条 Javadoc 注释,其中一条是针对类的,另一条是针对

    方法 main 的:

    演示print和println的示例程序

    public class Goodbye {

    打印问候语

    public static void main(String[] args) {

    System.out.print(Goodbye, ); 请注意其中的空格

    System.out.println(cruel world);

    }

    }

    类注释阐述了类的目的,而方法注释阐述了方法的作用。

    注意,这个示例还包含了一条以 打头的内嵌注释。一般而言,内嵌注释是

    对程序复杂部分进行诠释的短语,旨在帮助其他程序员理解和维护源代码。

    相反,Javadoc 注释更长,通常是完整的句子。它们阐述每个方法的功能,但

    不涉及其工作原理的细节,旨在让人无需查看源代码就能使用这些方法。

    要想让源代码易于理解,合适的注释和文档是必不可少的。另外别忘了,对于

    你编写的代码,未来阅读得最多的人就是你自己,届时你定将庆幸自己编写了优秀

    的文档。

    4.10 术语表

    实参

    调用方法时提供的值,其类型必须与相应形参的类型相同。

    调用

    执行方法。

    形参

    运行方法所需要的信息。形参也是变量,包含值和类型。

    执行流程

    Java 执行方法和语句的顺序,这种顺序并不一定是从上到下、从左到右的。

    参数传递

    将实参的值赋给形参变量的过程。

    局部变量

    在方法内声明的变量;在所属方法外不能访问。

    栈图

    各个方法中的变量的图形化表示。执行流程中的方法调用按从上到下的顺

    序堆叠。

    栈帧

    在栈图中表示特定方法中的变量和形参及其当前值的部分。

    特征标

    方法的第一行,定义了方法的名称、返回类型和形参。

    Javadoc

    读取 Java 源代码并生成 HTML 格式文档的工具。

    文档

    描述类或方法用法的注释。

    4.11 练习

    本章的示例代码位于仓库 ThinkJavaCode 的目录 ch04 中,有关如何下载这

    个仓库,请参阅前言中的“使用示例代码”一节。做以下的练习前,建议你先编译

    并运行本章的示例。如果你还没有阅读 A.4 节,那么现在正是阅读的好时机。该节介绍了对接受用

    户输入并显示输出的程序进行测试的一种高效方式。

    练习4-1

    这个练习的意义在于阅读包含多个方法的程序的代码,并确保你明白其执行流

    程。

    (1) 下面程序的输出是什么?务必准确地指出哪些地方有空格以及在哪些地方

    换行了。

    提示:先口头描述 ping 和 baffle 被调用时会做什么。

    (2) 绘制一个状态图,显示 ping 首次被调用时程序的状态。

    (3) 如果在方法 ping 的最后调用 baffle,结果将会如何?(我们将在下

    一章介绍其中的原因。)

    public static void zoop {

    baffle;

    System.out.print(You wugga );

    baffle;

    }

    public static void main(String[] args) {

    System.out.print(No, I );

    zoop;

    System.out.print(I );

    baffle;

    }

    public static void baffle {

    System.out.print(wug);

    ping;

    }

    public static void ping {

    System.out.println(.);

    }练习4-2

    这个练习旨在确保你明白如何编写和调用接受参数的方法。

    (1) 编写方法 zool 的第一行,这个方法包含三个形参:一个 int 形参和两

    个 String 形参。

    (2) 编写调用 zool 的代码,它传递的实参为值 11、你养的第一个宠物的名

    字以及你小时候居住的街道。

    练习4-3

    这个练习的目的在于将之前编写的代码封装到一个接受参数的方法中。做这个

    练习前,必须先完成练习 2-2。

    (1) 编写一个名为 printAmerican 的方法,让其接受参数 day、date、month 和 year,并以美国格式显示。

    (2) 对这个方法进行测试:在 main 中调用该方法并传递合适的实参。输出应

    类似于以下这样(只是日期可能不同):

    Saturday, July 22, 2015

    (3) 确定方法 printAmerican 正确无误后,再编写一个以欧洲格式显示日期

    的方法,并将其命名为 printEuropean。第 5 章 条件和逻辑

    不管输入如何,前几章中的程序每次运行时做的事情几乎相同。进行更复杂的

    计算时,通常需要程序根据输入作出反应,即检查特定的条件并生成相应的结果。

    本章将介绍能让程序作出决策的功能:一种名为 boolean 的数据类型、表示逻辑

    的运算符以及 if 语句。

    5.1 关系运算符

    关系运算符(relational operator)用于检查条件,如两个值是否相等或一

    个值是否大于另一个值。以下的表达式演示了关系运算符的用法:

    x == y x等于y

    x != y x与y不相等

    x > y x大于y

    x < y x小于y

    x >= y x大于或等于y

    x <= y x小于或等于y

    关系运算符的结果为 true 或 false 这两个特殊值中的一个。这些值属于

    boolean 数据类型;事实上,它们是仅有的两个 boolean 值。

    你可能熟悉这些运算,但注意,表示这些运算时,Java 使用的运算符不同于数

    学中使用的符号(如 =、≠和≤)。一种常见的错误是使用单个等号(=)而不是两个

    (==)。别忘了,= 是赋值运算符,而 == 是一个比较运算符。另外,没有诸如 =

    < 和 => 这样的 Java 运算符。

    关系运算符的两边必须兼容,例如,表达式 5 < 6 是非法的,因为 5 是一

    个 int,而 6 是一个 String。比较不同类型的数值时,Java 应用前面介绍过

    的赋值运算符的转换规则。

    例如,计算表达式 5 < 6.0 时,Java 自动将 5 转换为 5.0。大多数的关系运算符都不可用于字符串,但令人迷惑的是,== 和 != 可以,只是它们的行为并非你预期的那样,我们将在后面有所介绍。在此之前,不要将它

    们用于字符串,而应使用方法 equals:

    String fruit1 = Apple;

    String fruit2 = Orange;

    System.out.println(fruit1.equals(fruit2));

    fruit1.equals(fruit2) 的结果为 boolean 值 false。

    5.2 逻辑运算符

    Java 提供了三个逻辑运算符(logical operator):、|| 和 !,分别表

    示与、或、非。这些运算符的结果与其在英语中的含义类似。

    例如,如果 x 大于 0 且小于 10,那么 x > 0 x < 10 的结果为

    true;对于表达式 evenFlag || n \% 3 == 0,只要其中一个条件为 true,即

    如果 evenFlag 为 true 或数字 n 能被 3 整除,那么这个表达式的结果就为

    true。最后,运算符 ! 对 boolean 表达式求反,因此,如果 evenFlag 不为

    true,则 !evenFlag 为 true。

    逻辑运算符仅在必要时才计算第二个表达式的值。例如,true || anything

    的结果在任何情况下都为 true,因此 Java 无需计算表达式 anything 的值。同

    样,false anything 的结果在任何情况下都为 false。在可能的情况下忽略

    第二个操作数被称为短路(short circuit)求值,可与电路类比。短路求值可节

    省时间,在 anything 的值需要很长时间才能计算出时尤其如此。如果 anything

    可能出现问题的话,这还可以避免不必要的错误。

    如果你曾必须对包含逻辑运算符的表达式求反,以后也很可能遇到这样的情

    况。在这种情况下,德 · 摩根定律(De Morgan's laws)可以提供帮助:!(A B) 与 !A || !B 等价!(A || B) 与 !A !B 等价

    上述列表表明,要对逻辑表达式求反,可分别对每一项求反,并使用相反的运

    算符。运算符 ! 的优先级比 和 || 高,因此不需要将 !A 和 !B 分别放在括

    号中。

    德 · 摩根定律也适用于关系运算符。在这种情况下,对每一项求反意味着使

    用“相反”的运算符:!(x < 5 y == 3 ) 与 x > =5 || y! = 3 等价!(x >= 1 || y! = 7 ) 与 x < 1 y == 7 等价

    将这些示例大声地朗读出来可能会有所帮助。例如,“如果不希望 x 小于 5,且不希望 y 为 3,就意味着 x 必须大于或等于 5,且 y 不能为 3。”

    5.3 条件语句

    为了编写有用的程序,几乎都需要检查添加并采取相应的措施。条件语句

    (conditional statement)提供了这样的功能。if 语句是 Java 中最简单的条

    件语句:

    if (x > 0) {

    System.out.println(x is positive);

    }

    括号内的表达式被称为条件,如果它为 true,那么将执行大括号内的语句,否

    则将跳过这个代码块。括号内的条件可以是任何 boolean 表达式。

    还有一种包含两种可能性(分别由 if 和 else 标识)的条件语句。这些可能

    性称为分支(branch),由条件决定将执行哪个分支:

    if (x % 2 == 0) {

    System.out.println(x is even);

    } else { System.out.println(x is odd);

    }

    如果 x 除以 2 的余数为 0,则 x 为偶数,因此上述代码片段显示相应的消

    息。如果不满足这个条件,则执行第二条打印语句。由于要么满足条件,要么不满

    足条件,因此只有一个分支会被执行。

    对于只有一条语句的分支来说,大括号是可选的,因此前一个示例可以写成下

    面这样:

    if (x % 2 == 0)

    System.out.println(x is even);

    else

    System.out.println(x is odd);

    然而,即便大括号是可有可无的,最好不要省略,这样可避免在 if 或 else

    代码块中添加语句时因忘记加大括号而导致错误。

    if (x > 0)

    System.out.println(x is positive);

    System.out.println(x is not zero);

    这些代码没有正确地缩进,因此极具误导性。由于省略了大括号,只有第一个

    println 是 if 语句的一部分。在编译器看来,上述代码实际上是以下这样的:

    if (x > 0) {

    System.out.println(x is positive);

    }

    System.out.println(x is not zero);

    因此,在任何情况下都将执行第二条 println 语句。即便是经验丰富的程序

    员也会犯这样的错误,只要在网上搜索 goto fail 就会有所了解了。5.4 串接和嵌套

    有时需要检查多个相关的条件,并在多种措施中选择一种方式。其中一种方法

    是将一系列的 if 和 else 语句串接(chaining)起来:

    if (x > 0) {

    System.out.println(x is positive);

    } else if (x < 0) {

    System.out.println(x is negative);

    } else {

    System.out.println(x is zero);

    }

    你可以想串接多长就串接多长,但太长可能难以阅读。为提高可读性,可使用

    标准的缩进方式,如这里的示例所示。将语句和大括号对齐可降低出现语法错误的

    可能性。

    除了串接,还可以在一个条件语句中嵌套(nesting)另一个条件语句以作出

    复杂的决策。可将前面的示例重写为下面这样:

    if (x == 0) {

    System.out.println(x is zero);

    } else {

    if (x > 0) {

    System.out.println(x is positive);

    } else {

    System.out.println(x is negative);

    }

    }

    外面的条件语句有两个分支,第一个分支包含一条打印语句,第二个分支包含

    另一个条件语句,该条件语句也有两个分支。这两个分支都是打印语句,但它们本

    来也可以是条件语句。

    这样的嵌套结构很常见,但很难快速地阅读它们。因此我们必须使用正确的缩

    进,以便这种结构易于理解。5.5 标志变量

    要想存储 true 或 false 的值,需要使用 boolean 变量,而要创建

    boolean 变量,可像下面这样做:

    boolean flag;

    flag = true;

    boolean testResult = false;

    第 1 行是变量声明,第 2 行是赋值语句,而第 3 行在声明变量的同时给它

    赋值。由于关系运算符的结果为 boolean 值,因此可将比较结果存储在一个变量

    中:

    boolean evenFlag = (n % 2 == 0); n为偶数时为true

    boolean positiveFlag = (x > 0); x为正数时为true

    其中的括号并非必不可少,但可以让代码更容易理解。以这种方式定义的变量

    被称为标志(flag),因为它指出或“标志”着条件是否满足。

    定义标志变量后,就可以在条件语句中使用了:

    if (evenFlag) {

    System.out.println(n was even when I checked it);

    }

    注意,你无需这样书写:if (evenFlag == true),因为 evenFlag 就是

    boolean 值,可用于表示条件。同理,要检查标志是否为 false,可像下面这样

    做:

    if (!evenFlag) {

    System.out.println(n was odd when I checked it);

    }5.6 return语句

    return 语句允许还未到达末尾就终止方法。使用 return 语句的原因之一是

    检测到了错误条件:

    public static void printLogarithm(double x) {

    if (x <= 0.0) {

    System.err.println(Error: x must be positive.);

    return;

    }

    double result = Math.log(x);

    System.out.println(The log of x is + result);

    }

    这个示例定义了一个名为 printLogarithm 的方法,该方法将一个 double

    值作为形参(名为 x),用于检查 x 是否小于或等于 0,如果是这样的,那么就

    显示一条错误消息,再用 return 退出方法。这样将立即返回到调用这个方法的地

    方,而不执行这个方法的后面代码。

    这个示例使用了 System.err,这是一个 OutputStream,通常用于显示错误

    消息和警告。对于 System.err 的输出,有些开发环境用不同的颜色显示或在独立

    的窗口中显示。

    5.7 验证输入

    以下的方法调用了 5.6 节中的 printLogarithm:

    public static void scanDouble {

    Scanner in = new Scanner(System.in);

    System.out.print(Enter a number: );

    double x = in.nextDouble;

    printLogarithm(x);

    }

    这个示例调用了 nextDouble,因此 Scanner 将尝试读取一个 double 值。如果用户输入的是一个浮点数,Scanner 将把它转换为 double 值;但如果用户输

    入的是其他类型的值, Scanner 将引发 InputMismatchException 异常。

    为防范这种错误,可在分析前检查输入:

    public static void scanDouble {

    Scanner in = new Scanner(System.in);

    System.out.print(Enter a number: );

    if (!in.hasNextDouble{

    String word = in.next;

    System.err.println(word + is not a number);

    return;

    }

    double x = in.nextDouble;

    printLogarithm(x);

    }

    Scanner 类提供了方法 hasNextDouble,该方法用于检查能否将输入流中的

    下一个标记转换为 double 值。如果答案是肯定的,那么就可以调用

    nextDouble,且不会引发异常。如果答案是否定的,那么就显示一条错误消息并返

    回。从 main 方法返回将导致程序终止。

    5.8 递归方法

    介绍了条件语句后,现在可以探索程序能做的最神奇的事情之一了——递归

    (recursion)。思考下面的示例:

    public static void countdown(int n) {

    if (n == 0) {

    System.out.println(Blastoff!);

    } else {

    System.out.println(n);

    countdown(n - 1);

    }

    }这个方法名为 countdown,它将一个整数作为参数。如果这个参数为零,它就

    显示单词 Blastoff,否则就显示这个数字,再用实参 n-1 调用自己。调用自己的

    方法称为递归方法(recursive method)。

    如果在 main 中调用 countdown(3),结果将如何?

    这次执行 countdown 时,n==3;由于 n 不为零,因此它显示值3,再调用自己……

    这次执行 countdown 时,n==2;由于 n 不为零,因此它显示值2,再调

    用自己……

    这次执行 countdown 时,n==1;由于 n 不为零,因此它显示值

    1,再调用自己……

    这次执行 countdown 时,n==0;由于n 为零,因此它

    显示值“Blastoff!”再返回。

    使用 1 调用的 countdown 返回。

    使用 2 调用的 countdown 返回。

    使用 3 调用的 countdown 返回。

    至此返回了 main,于是输出类似于下面这样:

    3

    2

    1

    Blastoff!

    再来看一个示例。在这个示例中,我们将重新编写 4.3 节中的方法 newLine

    和 threeLine:

    public static void newLine {

    System.out.println;

    }

    public static void threeLine {

    newLine;

    newLine;

    newLine;

    }虽然这些方法管用,但如果要显示 2 个或 100 个空行,它们并不能提供帮

    助。下面是一种更佳的解决方案:

    public static void nLines(int n) {

    if (n > 0) {

    System.out.println;

    nLines(n - 1);

    }

    }

    这个方法将一个整数(n)作为形参,并显示了 n 个空行。其结构与

    countdown 类似:只要 n 大于零,就显示一个空行,然后调用自己再显示 n-1

    个空行。显示的总空行数为 1+(n - 1),即我们希望的 n 个。

    5.9 递归栈图

    第 4 章用栈图来表示程序在方法调用期间的状态,使用相同的图可让递归方法

    更容易解释。

    前面说过,每当方法被调用时,Java 都会创建一个新的栈帧,其中包含该方法

    的形参和变量。图 5-1 是使用 3 调用 countdown 时的栈图。图 5-1:程序 countdown 的栈图

    根据约定,main 的栈帧位于最上面,而栈是向下延伸的。main 的栈帧是空

    的,因为它不包含任何变量。(它包含形参 args,但由于没用这个形参,因此没有

    在栈图中列出。)

    countdown 有四个栈帧,分别与形参 n 的不同值对应。在最后一个栈帧中

    n=0 被称为基线条件(base case)。因为它没有执行递归调用,所以下 ......

您现在查看是摘要介绍页, 详见PDF附件(3177KB,313页)