内容


Java 编程简介,第 1 部分

Java 语言基础

Java 平台上的面向对象编程

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: Java 编程简介,第 1 部分

敬请期待该系列的后续内容。

此内容是该系列的一部分:Java 编程简介,第 1 部分

敬请期待该系列的后续内容。

了解有望从本文中获得哪些收获,以及如何最充分地掌握本文中的内容。

关于本教程

这个分为两部分的 Java 编程简介教程适用于不熟悉 Java 技术的软件开发人员。学习完这两个部分后,即可利用 Java 语言和平台,启动并运行面向对象的编程 (OOP) 和实际的应用程序开发。

第 1 部分将对使用 Java 语言的 OOP 进行分步介绍。该教程首先将概述 Java 平台和语言,随后指导您设置一个由 Java Development Kit (JDK) 和 Eclipse IDE 组成的开发环境。在了解了开发环境的组成部分之后,就可以开始着手学习基本的 Java 语法。

第 2 部分将介绍更高级的语言特性,包括正则表达式、泛型、I/O 和序列化。第 2 部分中的编程示例使用第 1 部分中开始开发的 Person 对象作为基础。

目标

完成第 1 部分后,您将熟悉基本的 Java 语言语法,并能编写简单的 Java 程序。随后的“Java 编程简介,第 2 部分:实际应用程序的构造”将以此为基础。

前提条件

本教程适用于没有体验过 Java 代码或 Java 平台的软件开发人员。教程中包含 OOP 概念的概述。

系统要求

要完成本教程中的练习,需要安装并设置一个开发环境,其中包含:

  • 来自 Oracle 的 JDK 8
  • Eclipse IDE for Java Developers

教程中包含二者的下载和安装说明。

推荐的系统配置是:

  • 一个支持 Java SE 8,至少拥有 2GB 内存的系统。Linux®、Windows®、Solaris® 和 Mac OS X 上都支持 Java 8。
  • 至少 200MB 的磁盘空间,用于安装软件组件和示例。

Java 平台概述

Java 技术用于为各种各样的环境开发应用程序,从用户设备到异构企业系统。本节将总体概述 Java 平台和它的组件。

Java 语言

像任何编程语言一样,Java 语言拥有自己的结构、语法规则和编程范例。Java 语言的编程范例基于 OOP 的概念,此概念受 Java 语言支持。

Java 语言是 C 语言的一种衍生语言,所以它的语法规则与 C 非常相似。例如,代码块被模块化为方法并用花括号({})分隔,变量在使用之前需要进行声明。

在结构上,Java 语言以作为开头。包是 Java 语言的名称空间机制。包中包含类,而类中包含方法、变量、常量等。您将在本教程中了解 Java 语言的各个部分。

Java 编译器

为 Java 平台编写程序时,需要在 .java 文件中编写源代码,然后编译它们。编译器依据语言的语法规则来检查代码,然后将字节码写入 .class 文件中。字节码是一组将在 Java 虚拟机 (JVM) 上运行的指令。通过添加这一抽象级别,使 Java 编译器不同于其他语言编译器,它会编写适合用来运行程序的 CPU 芯片集的指令。

JVM

在运行时,JVM 读取并解释 .class 文件,在作为 JVM 编写目标的原生硬件平台上执行程序的指令。JVM 像 CPU 解释汇编语言指令一样解释字节码。不同之处在于,JVM 是一个专为特定平台编写的软件。JVM 是 Java 语言的“编写一次、随处运行”原则的核心。您的代码可在任何拥有合适的 JVM 实现的芯片集上运行。JVM 可用于 Linux 和 Windows 等主要平台,在用于手机和 Hobbyist 芯片的 JVM 中也已实现了 Java 语言的子集。

垃圾收集器

Java 平台不会强迫您时刻关注内存分配(或使用第三方库来完成此工作),而是提供了开箱即用的内存管理功能。Java 应用程序在运行时创建了一个对象实例时,JVM 会自动从为该对象分配内存空间— 堆是一个专门为您的程序留用的内存池。Java 垃圾收集器在后台运行,跟踪记录应用程序不再需要哪些对象,并回收它们占用的内存。这种内存处理方法称为隐式内存管理,因为它不要求编写任何内存处理代码。垃圾收集是 Java 平台性能的基本特征之一。

Java Development Kit

下载 Java Development Kit (JDK) 后 —除了编译器和其他工具外— 您还会获得一个包含预构建的实用程序的完整类库,该库可帮助您完成大部分常见的应用程序开发任务。了解 JDK 包和库的作用范围的最佳方式是查阅 JDK API 文档

Java 运行时环境

Java 运行时环境(JRE;也称为 Java 运行时)包含运行使用 Java 语言编写的程序所需的 JVM、代码库和运行用 Java 语言编写的程序所需的组件。JRE 可用于多种平台。您可以依据 JRE 许可条款,自由地将 JRE 随您的应用程序一起重新分发,为应用程序的用户提供一个运行您的软件的平台。JRE 包含在 JDK 中。

设置 Java 开发环境

在本节中,将下载并安装 JDK 和 Eclipse IDE 的最新版本,并设置 Eclipse 开发环境。

如果已经安装 JDK 和 Eclipse IDE,您可能想要跳到“Eclipse 入门”部分或它后面的“面向对象编程概念”部分。

您的开发环境

JDK 包含一组用于编译和运行 Java 代码的命令行工具,其中包括 JRE 的一个完整副本。可以使用这些工具开发应用程序,但大部分开发人员还喜欢 IDE 的其他功能、任务管理和可视界面。

Eclipse 是一个用于 Java 开发的流行的开源 IDE。Eclipse 处理代码编译和调试等基本任务,以便您可以集中精力编写和测试代码。此外,可以使用 Eclipse 将源代码文件组织到项目中,编译和测试这些项目,然后将项目文件存储在任意多个源代码存储库中。您需要一个已安装的 JDK 来使用 Eclipse 执行 Java 开发。如果下载一个 Eclipse bundle,它附带了 JDK。

安装 JDK

按照以下步骤下载并安装 JDK:

  1. 浏览到 Java SE Downloads 并单击 Java Platform (JDK) 框,以显示最新版 JDK 的下载页面。
  2. 同意您想要下载的版本的许可条款。
  3. 选择与您的操作系统和芯片架构匹配的下载。

Windows

  1. 在提示时,将文件保存到您的硬盘驱动器。
  2. 完成下载后,运行安装程序。将 JDK 安装到硬盘驱动器中容易记住的位置,比如 C:\home\Java\jdk1.8.0_92。(像在本例中一样,将更新号码编码到您选择的安装目录的名称中是一个好主意。)

OS X

  1. 完成下载后,双击它来实现挂载。
  2. 运行安装程序。不需要选择将 JDK 安装在何处。可以运行 /usr/libexec/java_home -1.8 来查看您的 Mac 上的 JDK 8 位置。显示的路径类似于 /Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home。

请参阅 JDK 8 和 JRE 8 安装了解更多信息,包括 Solaris 或 Linux 上的安装说明。

您的计算机上现在已有一个 Java 环境。接下来,将安装 Eclipse IDE。

安装 Eclipse

要下载并安装 Eclipse,请执行以下步骤:

  1. 浏览到 Eclipse 包下载页面
  2. 单击 Eclipse IDE for Java Developers
  3. 在右侧的 Download Links 下,选择您的平台(该网站可能已检测出您的操作系统类型)。
  4. 单击想要从中下载的镜像;然后将该文件保存到您的硬盘驱动器。
  5. 完成下载后,打开该文件并运行安装程序,接受默认设置。

设置 Eclipse

Eclipse IDE 是一种基于 JDK 的有用抽象,但它仍需要访问 JDK 及其各种工具。使用 Eclipse 编写 Java 代码之前,必须告诉它 JDK 在何处。

要设置 Eclipse 开发环境,请执行以下操作:

  1. 从本地硬盘启动 Eclipse。(我的案例中,该位置是 /Users/sperry/eclipse/java-neon。)
  2. 在询问想要打开哪个工作区时,选择默认设置。
  3. 关闭 Welcome to Eclipse 窗口。(每次进入一个新工作区时,都会显示该欢迎窗口。可通过取消选择“Always show Welcome at start up”复选框来禁用此行为。)
  4. 选择 Preferences > Java > Installed JREs。图 1 中的 Eclipse 设置窗口中已突出显示了这个 JRE 选项。
    图 1. 配置 Eclipse 使用的 JDK
    Eclipse 中的正确 JDK JRE 设置的屏幕截图
    Eclipse 中的正确 JDK JRE 设置的屏幕截图
  5. 确保 Eclipse 指向您与 JDK 一起下载的 JRE。如果 Eclipse 没有自动检测出您安装的 JDK,可单击 Add...,在下一个对话框中单击 Standard VM,然后单击 Next
  6. 指定 JDK 的主目录(比如 Windows 上的 C:\home\jdk1.8.0_92),然后单击 Finish
  7. 确认选择了您想要使用的 JDK 并单击 OK

现在已设置并准备好 Eclipse 供您创建项目,以及编译和运行 Java 代码。下一节将帮助您熟悉 Eclipse。

Eclipse 入门

Eclipse 不仅仅是一个 IDE;它还是一个完整的开发生态系统。本节将简要介绍实际使用 Eclipse 执行 Java 开发。

Eclipse 开发环境

Eclipse 开发环境有 4 个主要组件:

  • 工作区
  • 项目
  • 透视图
  • 视图

Eclipse 中的主要组织单元是工作区。一个工作区包含您的所有项目透视图是一种查看每个项目的方法(因此而得名“透视图”),一个透视图中包含一个或多个视图

图 2 显示了 Java 透视图,它是 Eclipse 的默认透视图。在启动 Eclipse 时可以看到此透视图。

图 2. Eclipse Java 透视图
Eclipse IDE 启动屏幕的屏幕截图,其中显示了默认的 Java 透视图。
Eclipse IDE 启动屏幕的屏幕截图,其中显示了默认的 Java 透视图。

Java 透视图包含开始编写 Java 应用程序所需的工具。图 2 中所示的每个选项卡式窗口都是 Java 透视图的一个视图。Package Explorer 和 Outline 是两个特别有用的视图。

Eclipse 环境是高度可配置的。每个视图都可停靠,所以可以在 Java 透视图中移动它们,将它们放在想要放置的位置。但就现在而言,我们将保持默认的透视图和视图设置。

创建一个项目

按照以下步骤创建一个新的 Java 项目:

  1. 单击 File > New > Java Project... 启动 New Java Project 向导,如图 3 所示。
    图 3. New Java Project 向导
    New Java Project 向导的屏幕截图
    New Java Project 向导的屏幕截图
  2. 输入 Tutorial 作为项目名称,并使用在打开 Eclipse 时打开的工作区位置。
  3. 验证您正在使用的 JDK。
  4. 单击 Finish 接受项目设置并创建项目。

您现在已创建了一个新的 Eclipse Java 项目和源代码文件夹。您的开发环境已经准备好大显身手。但是,理解 OOP 范例(本教程中的接下来两节将会介绍)至关重要。

面向对象编程的概念和原理

Java 语言(基本上)是面向对象的。本节将介绍 OOP 语言概念,并使用结构化编程作为一个对比。

什么是对象?

面向对象的语言遵循的编程模式不同于结构化编程语言,比如 C 和 COBOL。结构化编程范例是高度面向数据的:您拥有数据结构,然后程序指令会处理该数据。面向对象的语言(比如 Java 语言)将数据和程序指令组合到对象中。

对象是一个独立的实体,它仅包含属性和行为,不含其他任何东西。在面向对象的语言中,数据和程序逻辑被组合在一起,而不是通过字段(属性)提供一个数据结构,并将该结构传递给处理它的所有程序逻辑(行为)。这种组合可能出现在完全不同的粒度级别上,从细粒度的对象(比如 Number)到粗粒度的对象(比如一个大型银行应用程序中的 FundsTransfer 服务)。

父对象和子对象

父对象用作派生更复杂的子对象的结构基础。子对象看起来类似于父对象,但更加特殊化。在面向对象的范例中,可以重用父对象的通用属性和行为,也可以向子对象添加不同的属性和行为。

对象通信和协调

对象通过发送消息(在 Java 用语中称为方法调用)与其他对象进行通信。此外,在面向对象的应用程序中,程序代码会协调对象之间的活动,以便在特定应用程序域的上下文内执行任务。

对象总结

一个精心编写的对象:

  • 有明确定义的边界
  • 执行一组有限的活动
  • 只知道它的数据和它完成活动所需的其他所有对象

实质上,对象是一个离散的实体,它仅在其他对象上拥有必要的依赖关系来执行其任务。

是时候看看 Java 对象看起来像什么了。

示例:person 对象

第一个示例基于一种常见的应用程序开发场景:一个通过 Person 对象表示的人。

从对象的定义可以知道,一个对象有两个主要元素:属性和行为。下面将介绍这些元素如何应用到 Person 对象。

根据经验,可将对象的属性视为名词,将行为视为动词

属性(名词)

一个人可能有哪些属性?一些常见属性包括:

  • 名称
  • 年龄
  • 身高
  • 体重
  • 眼睛颜色
  • 性别

您可能会想到更多属性(而且始终可以在以后添加更多属性),但此列表是一个不错的开端。

行为(动词)

一个真正的人可以做各种各样的事,但对象行为通常与某种应用程序上下文相关。例如,在业务应用程序上下文中,您可能想询问您的 Person 对象,“您的体重指数 (BMI) 是多少?”作为响应,Person 将使用它的身高和体重属性的值来计算 BMI。

Person 对象内部可能隐藏着更复杂的逻辑,不过现在,我们假设 Person 具有以下行为:

  • 计算 BMI
  • 输出所有属性

状态和字符串

状态是 OOP 中的一个重要概念。对象的状态在任何时刻都由它的属性的值来表示。

对于 Person,它的状态由姓名、年龄、身高和体重等属性来定义。如果想提供多个属性的列表,可以使用 String 类来完成此任务,稍后我们会更详细地介绍该类。

结合使用状态和字符串的概念,您可以对 Person 说:“向我提供你的属性列表(或 String)来告诉我你是谁”。

OOP 的原则

如果您拥有结构化编程背景,您可能还不清楚 OOP 的价值主张。毕竟,一个人的属性和检索(并转换)这些值的任何逻辑都可使用 C 或 COBOL 编写。如果理解了 OOP 的定义原则,OOP 范例的优势就会更加明显:封装继承多态性

封装

回想一下,对象是离散的(或独立的)。此特征就是封装的工作原理。隐藏是另一个有时用于表达对象的独立、受保护性质的术语。

无论使用哪个术语,重要的是对象在它的状态和行为与外部世界之间保持一个界线。像真实世界中的物体一样,计算机编程中使用的对象与使用它们的应用程序中的不同类别的对象之间有着各种各样的关系。

在 Java 平台上,可以使用访问修饰符(将在后面介绍)将对象关系从公共改为私有。公共访问是完全开放的,而私有访问意味着对象的属性只能在对象自身内访问。

公共/私有边界实施了面向对象的封装原则。在 Java 平台上,可以逐个对象地改变该边界的强度。封装是 Java 语言的一个强大特性。

继承

在结构化编程中,常常会复制一个结构,为它提供一个新名称,然后添加或修改属性,使新实体(比如一个 Account 记录)不同于它的原始来源。随着时间的推移,此方法会生成大量重复代码,这可能带来维护问题。

OOP 引入了继承的概念,特殊化的类(无需额外的代码)可“复制”它们要特殊化的来源类的属性和行为。如果需要更改其中一些属性或行为,可以重写它们。您更改的唯一的源代码是创建特殊化的类所需的代码。源对象称为父对象,新的特殊化对象称为子对象— 我们已介绍过这两个术语。

假设您正在编写一个人力资源应用程序,并想要使用 Person 类作为一个名为 Employee 的新类的基类(也称为超类)。作为Person 的子类,Employee 将拥有 Person 类的所有属性,以及更多属性,比如:

  • 纳税人识别编号
  • 员工编号
  • 工资

继承使创建新的 Employee 类变得很容易,不需要手动复制所有 Person 代码。

多态性

多态性是一个比封装和继承更难掌握的概念。事实上,多态性表示属于某个分层结构的同一个分支的对象,在发送相同的消息时(也就是说,在被告知做同一件事时)可通过不同方式表明该行为。

要了解如何将多态性应用到业务应用程序上下文中,可返回到 Person 示例。还记得告诉 Person 将它的属性格式化成一个 String 吗?多态性使 Person 可以依据它的 Person 类型,以各种不同的方式表示它的属性。

多态性是在 Java 平台上的 OOP 中将会遇到的更复杂概念之一,不属于此入门课程的讨论范围。后续小节将更深入地探索封装和继承。

不是纯粹的面向对象语言

有两种性质可区分 Java 语言与纯粹的面向对象语言(比如 Smalltalk)。首先,Java 语言是对象和原语类型的 一种混合。其次,可以使用 Java 编写代码,将一个对象的内部情况向其他任何使用它的对象公开。

Java 语言为您提供了必要工具来遵循合理的 OOP 原则,并生成合理的面向对象的代码。因为 Java 不是纯粹面向对象的语言,所以必须在编写代码的方式上运用一些规则—该语言不会强制要求执行正确的操作,所以您必须学会自我约束。“编写良好的 Java 代码”部分中将提供一些技巧。

Java 语言入门

一篇教程不可能介绍完所有的 Java 语言语法。第 1 部分的剩余部分将重点介绍该语言的基础知识,为您提供足够的知识并练习编写简单的程序。OOP 与对象密切相关,所以本节首先会介绍两个与 Java 语言如何处理它们明确相关的主题:保留字和 Java 对象的结构。

保留字

像任何编程语言一样,Java 语言指定了一些编译器认为具有特殊意义的关键字。出于该原因,您不能使用它们来命名您的 Java 构造。保留字(也称为关键字)非常少:

abstract
assert
boolean
break
byte
case
catch
char
class
const
continue
default
do
double
else
enum
extends
final
finally
float
for
goto
if
implements
import
instanceof
int
interface
long
native
new
package
private
protected
public
return
short
static
strictfp
super
switch
synchronized
this
throw
throws
transient
try
void
volatile
while

您也不能使用 truefalsenull(严格意义上讲,它们是 字面常量 而不是关键字)来命名 Java 构造

使用 IDE 进行编程的一个优势是,它可以对保留字使用语法颜色。

Java 类的结构

类是包含属性和行为的离散实体(对象)的蓝图。类定义了对象的基本结构,在运行时,您的应用程序会创建对象的实例。对象拥有明确定义的边界和状态,在正确请求对象时,它们可以执行相应的操作。每种面向对象的语言都拥有关于如何定义类的规则。

在 Java 语言中,类的定义如清单 1 所示:

清单 1. 类定义
package packageName;
import ClassNameToImport; 
accessSpecifier class ClassName {
  accessSpecifier dataType variableName [= initialValue];
  accessSpecifier ClassName([argumentList]) {
    constructorStatement(s)
  }
  accessSpecifier returnType methodName ([argumentList]) {
    methodStatement(s)
  }
  // This is a comment
  /* This is a comment too */
  /* This is a
     multiline
     comment */
}

清单 1 包含各种类型的构造,第 1 行是 package,第 2 行是 import,第 3 行是 class。这 3 种构造都属于保留字,所以它们必须保持清单 1 中的准确格式。我为清单 1 中的其他构造提供的名称描述了它们表示的概念。

请注意,清单 1 中第 11 到 15 行是注释行。在大多数编程语言中,程序员都可添加注释来帮助描述代码。Java 语法允许采用单行和多行注释。

// This is a comment
/* This is a comment too */
/* This is a
multiline
comment */

单行注释必须包含在一行上,但可以使用邻近的多个单行注释来形成一个注释块。多行注释以 /* 开头,必须以 */ 终止,而且可以分布在任意数量的行上。

接下来,我们将详细介绍清单 1 中的构造,首先是 package

打包类

在 Java 语言中,可以为类选择名称,比如 AccountPersonLizardMan。有时,可能最终使用了同一个名称来表达两个稍微不同的概念。这种情形称为名称冲突,经常发生。Java 语言使用来解决这些冲突。

Java 包是一种提供名称空间的机制— 名称空间是一个区域,名称在该区域内是唯一的,但在其外部可能不是唯一的。要唯一地标识某个构造,必须包含它的名称空间来完全限定它。

包也是构造包含离散功能单元的更复杂应用程序的好方法。

要定义包,可使用 package 关键字,后跟一个合法的包名称,并以一个分号结尾。包名称通常遵循这种事实标准模式:

package  orgType.orgName.appName.compName;

这个包定义可分解为不同的部分:

  • orgType 是组织类型,比如 comorgnet
  • orgName 是组织的域名称,比如 makotojavaoracleibm
  • appName 是应用程序的缩写名称。
  • compName 是组件的名称。

本课程将使用此约定,建议坚持使用它来定义包中的所有 Java 类。(Java 语言没有强制要求您遵循这种包约定。您完全不需要指定一个包,但在这种情况下,您的所有类都必须有唯一的名称且位于默认包中。)

导入语句

类定义中的下一部分(返回参考清单 1)是导入语句。导入语句告诉 Java 编译器在何处查找您在代码中引用的类。任何非无效类都会使用其他类来实现某种功能,导入语句是您向 Java 编译器告知这些类的方式。

导入语句看起来通常类似于:

import ClassNameToImport;

您指定 import 关键字,后跟想要导入的类,然后是一个分号。类名应是完全限定的,这意味着类名中应包含它的包。

要导入一个包中的所有类,可以将 .* 放在包名称后。例如,这条语句导入 com.makotojava 包中的每个类:

import com.makotojava.*;

但是,导入整个包可能会降低代码的可读性,所以推荐使用完全限定名称来仅导入所需的类。

类的声明

要在 Java 语言中定义一个对象,必须声明一个类。可以将类看作是对象的模板,就像一个饼干模子。

清单 1 包含这个类的声明:

accessSpecifier class ClassName {
  accessSpecifier dataType variableName [= initialValue];
    accessSpecifier ClassName([argumentList]) {
    constructorStatement(s)
  }
  accessSpecifier returnType methodName([argumentList]) {
    methodStatement(s)
  }
}

类的 accessSpecifier 可拥有多个值,但该值通常是 public。您很快会看到 accessSpecifier 的其他值。

您可以采用您想要的任意方式对类进行命名,但根据约定应使用驼峰式大小写:以一个大写字母开头,将串联的每个单词的首字母大写,将其他所有字母小写。类名应仅包含字母和数字。坚持这些准则,可确保代码更容易供其他遵循相同约定的人使用。

变量和方法

类可以拥有两种类型的成员变量方法

变量

类的变量值可以区分该类的每个实例并定义它的状态。这些值通常称为实例变量。一个变量包含:

  • 一个 accessSpecifier
  • 一个 dataType
  • 一个 variableName
  • 一个可选的 initialValue

可能的 accessSpecifier 值包括:

  • public:任何包中的任何对象都能看到该变量。(不要使用此值;请参阅公共变量侧栏。)
  • protected:相同包中定义的任何对象或(在任何包中定义的)一个子类都可以看到该变量。
  • 没有说明符(也称为友好包私有访问):只有在相同包中定义了其类的对象才能看到该变量。
  • private:只有包含该变量的类能够看到它。

变量的 dataType 取决于该变量是什么 — 它可能是一种原语类型或另一种类类型(稍后会更详细地介绍)。

variableName 由您自己决定,但根据约定,变量名称应使用驼峰式大小写约定,除了以小写字母开头的变量名称。(这种样式有时称为小驼峰式大小写。)

现在暂时不要担忧 initialValue,只需知道您在声明一个实例变量时可以将其初始化。(否则,编译器会为您生成在实例化该类时设置的默认值。)

示例:Person 的类定义

下面这个示例总结了您目前所学的知识。清单 2 是 Person 的类定义。

清单 2. Person 的基类定义
package com.makotojava.intro;

public class Person {
   private String name;
   private int age;
   private int height;
   private int weight;
   private String eyeColor;
   private String gender;
}

Person 的这个基类定义目前不是很有用,因为它仅定义了 Person 的属性(而且还是私有属性)。要变得更完整,Person 类还需要行为 — 即方法

方法

类的方法定义了它的行为。

方法可分为两种主要类别:构造方法;以及其他所有方法,这些方法具有许多类型。构造方法仅用于创建类的实例。其他类型的方法可用于几乎任何应用程序行为。

清单 1 中的类定义给出了定义方法结构的方法,该结构包括以下元素:

  • accessSpecifier
  • returnType
  • methodName
  • argumentList

这些结构元素在方法定义中的组合也称为方法的签名

现在仔细查看两种方法类别,先查看构造方法。

构造方法

可使用构造方法指定如何实例化类。清单 1 以抽象形式给出了构造方法的声明语法;这里再次给出了它:

accessSpecifier ClassName([argumentList]) {
  constructorStatement(s)
}

构造方法的 accessSpecifier 与变量的 accessSpecifier 相同。构造方法的名称必须与类名匹配。因此,如果您将类命名为 Person,构造方法的名称也必须也是 Person

对于任何其他非默认构造方法(参见构造方法是可选的侧栏),需要传递一个 argumentList,其中包含一个或多个:

argumentType argumentName

argumentList 中的参数用逗号分隔,而且任何两个参数不能同名。 argumentType 为原语类型或另一种类类型(与变量类型相同)。

包含构造方法的类定义

现在来看看在通过两种方式添加了创建 Person 对象的能力后会发生什么:通过使用 no-arg 构造方法和通过初始化一个部分属性列表。

清单 3 展示了如何创建构造方法,以及如何使用 argumentList

清单 3. 包含构造方法的 Person 类定义
package com.makotojava.intro;
public class Person {
  private String name;
  private int age;
  private int height;
  private int  weight;
  private String eyeColor;

  private String gender;
  public Person() {
    // Nothing to do...
  }

  public Person(String name, int age, int height, int weight String eyeColor, String gender) {
    this.name = name;
    this.age = age;
    this.height = height;
    this.weight = weight;
    this.eyeColor = eyeColor;
    this.gender = gender;
  }
}

请注意,清单 3 中使用了 this 关键字来执行变量赋值。this 关键字是“this object”的 Java 简写形式,在引用两个具有相同名称的变量时必须使用它。在本例中,age 既是一个构造方法参数,也是一个类变量,所以 this 关键字可帮助编译器辨别它属于哪种情况。

Person 对象变得越来越有趣,但它需要更多行为。为此,您需要更多方法。

其他方法

构造方法是一种具有特定功能的特定方法类型。类似地,其他许多类型的方法执行 Java 程序中的特定功能。从本节开始到全教程结束,我们将探索其他方法类型。

返回到清单 1 中,可以看到方法的声明方式:

accessSpecifier returnType methodName ([argumentList]) {
  methodStatement(s)
}

其他方法看起来非常像构造方法,但有两处例外。 首先,您可为其他方法提供您喜欢的任何名称(但是,肯定要遵守某些规则)。我推荐遵守以下约定:

  • 以小写字母开头。
  • 避免使用数字,除非绝对必须使用它们。
  • 仅使用字母字符。

第二,不同于构造方法,其他方法有一个可选的返回类型

Person 的其他方法

有了这些基本信息,就可以在清单 4 中看到在向 Person 对象添加一些方法后发生的情况。(为简洁起见,我省略了构造方法。)

清单 4. 包含一些新方法的 Person
package com.makotojava.intro;

public class Person {
   private String name;
   private int age;
   private int height;
   private int  weight;
   private String eyeColor;
   private String gender;

   public String getName() { return name; }
   public void setName(String value) { name = value; }
   // Other getter/setter combinations...
}

请注意清单 4 中有关“getter/setter combinations”的注释。稍后会更多地使用 getter 和 setter。就现在而言,您只需知道 getter 是一个检索属性值的方法,setter 是一个修改该值的方法。 清单 4 仅给出了一种 getter/setter 组合(针对 Name 属性),但您可用类似方式定义更多组合。

清单 4 中可以注意到,如果某个方法没有返回值,则必须在该方法的签名中指定 void 返回类型来告知编译器。

静态和实例方法

通常会使用两种类型的(非构造)方法:实例方法静态方法。实例方法依赖于特定对象实例的状态来实现其行为。静态方法有时也称为类方法,因为它们的行为不依赖于任何一个对象的状态。静态方法的行为是在类级别上发生的。

静态方法主要用于实用程序;您可将它们视为全局方法(类似于 C 语言),但将该方法的代码与定义它的类放在一起。

例如,在整个教程中,您都会使用 JDK Logger 类向控制台输出信息。要创建一个 Logger 类实例,不需要实例化 Logger 类;而是应该调用一个名为 getLogger() 的静态方法。

在类上调用静态方法的语法不同于在对象上调用方法的语法。您还会使用包含该静态方法的类的名称,如这个调用中所示:

Logger l = Logger.getLogger("NewLogger");

在这个示例中,Logger 是类名,getLogger(...) 是方法名。因此,要调用静态方法,不需要对象实例,只需类名即可。

您的第一个 Java 类

是时候将前面几节中学到的知识融会贯通,并开始编写一些代码了。本节将指导您使用 Eclipse Package Explorer 声明一个类,并向它添加变量和方法。您将学习如何使用 Logger 类密切关注应用程序的行为,以及如何使用 main() 方法作为 测试平台

创建一个包

如果未在(Java 透视图中的)Package Explorer 视图中,可在 Eclipse 中通过 Window > Perspective > Open Perspective 进入该视图。您将通过设置来创建您的第一个 Java 类。第一步是创建一个放置该类的位置。包是名称空间构造,而且它们可方便地直接映射到文件系统的目录结构。

您可以不使用默认包(这通常是一个坏主意),而是专门为您正在编写的代码创建一个包。单击 File > New > Package 启动 Java Package 向导,如图 4 所示。

图 4. Eclipse Java Package 向导
Eclipse Package 向导的屏幕截图
Eclipse Package 向导的屏幕截图

com.makotojava.intro 键入到 Name 文本框中并单击 Finish。可以在 Package Explorer 中看到创建的新包。

声明类

可以从 Package Explorer 中通过多种方式创建类,但最简单的方法是右键单击刚创建的包并选择 New > Class...。New Class 对话框将会打开。

在 Name 文本框中,键入 Person,然后单击 Finish

新类将显示在编辑窗口中。建议关闭一些在您首次打开 Java 透视图时其中默认打开的视图(Problems、Javadoc 等),以便更容易查看您的源代码。(在下次打开 Eclipse 并转到 Java 透视图时,Eclipse 会记住您不想看到这些视图。)图 5 显示了一个打开了必要视图的工作区。

图 5. 一个井然有序的工作区
Eclipse Package 向导的编辑窗口的屏幕截图
Eclipse Package 向导的编辑窗口的屏幕截图

Eclipse 为您生成了一个 shell 类,并在顶部包含 package 语句。您现在只需充实该类。可以通过 Window > Preferences > Java > Code Style > Code Templates 配置 Eclipse 如何生成新类。为了简便起见,我们使用了 Eclipse 的开箱即用的代码生成功能。

图 5中,请注意新的源代码文件名称旁边的星号 (*),它表示我已进行了修改。另请注意,该代码尚未保存。接下来请注意我在声明 Name 属性时犯了一个错误:我将 Name 的类型声明为了 Strin。编译器无法找到对这样一个类的引用,所以将它标记为一个编译错误(在 Strin 下添加了红色的波浪线)。当然,我在 Strin 的末尾添加一个 g 来修复我的错误。这是对使用 IDE 而不是使用命令行工具进行软件开发的一个小示范。可通过将类型更改为 String 来更正该错误。

添加类变量

清单 3 中,已经开始充实 Person 类,但我没有过多地解释语法。现在,我将正式定义如何添加类变量。

回想一下,一个变量有一个 accessSpecifier、一个 dataType、一个 variableName 和一个可选的 initialValue。之前,您简单了解了如何定义 accessSpecifiervariableName。现在,您已看到一个变量可以拥有的 dataType

dataType 可以是原语类型或对另一个对象的引用。例如,可以注意到 Age 是一个 int(原语类型),Name 是一个 String(一个对象)。JDK 提供了大量有用的类,比如 java.lang.String,而且不需要导入 java.lang 包中的类(Java 编译器采用的一种简写形式)。但无论 dataType 是 JDK 类(比如 String),还是用户定义的类,语法都基本相同。

表 1 给出了您可能经常看到的 8 种原语数据类型,包括没有显式初始化某个成员变量的值时这些原语采用的默认值。

表 1. 原语数据类型
类型大小默认值值范围
boolean不适用falsetrue 或 false
byte8 位0-128 到 127
char16 位(无符号)\u0000' \u0000' 到 \uffff' 或 0 到 65535
short16 位0-32768 到 32767
int32 位0-2147483648 到 2147483647
long64 位0-9223372036854775808 到 9223372036854775807
float32 位0.01.17549435e-38 到 3.4028235e+38
double64 位0.04.9e-324 到 1.7976931348623157e+308

内置记录

在进一步学习编码之前,需要知道您的程序如何告诉您它们在做什么。

Java 平台包含 java.util.logging 包,这是一种以可读形式收集程序信息的内置记录机制。Logger 是您通过对 Logger 类执行静态方法调用所创建的命名实体:

import java.util.logging.Logger;
//...
Logger l = Logger.getLogger(getClass().getName());

在调用 getLogger() 方法时,向它传递了一个 String。就现在而言,只需养成传递您正编写的代码所在类的名称的习惯。在任何常规(即非静态)方法中,前面的代码始终引用该类的名称并将它传递给 Logger

如果要在一个静态方法内调用一次 Logger,可引用您所在的类的名称:

Logger l = Logger.getLogger(Person.class.getName());

在这个示例中,您所在的类是 Person 类,所以您引用了一个称为 class 的特殊文字,它检索 Class 对象(将在以后更详细介绍)并获取其 Name 属性。

本教程的“编写良好的 Java 代码”部分包含一个如何进行记录的技巧。

开始执行测试之前,首先进入 Person 的 Eclipse 源代码编辑器,并将此代码添加到清单 3 中的 public class Person { 后,类似于这样:

package com.makotojava.intro;

public class Person {
  private String name;
  private int age;
  private int height;
  private int weight;
  private String eyeColor;
  private String gender;
}

Eclipse 拥有一个方便的代码生成器来生成 getter 和 setter(以及其他内容)。要尝试使用代码生成器,请将鼠标光标放在 Person 类定义上(即放在类定义中的单词 Person 上),然后单击 Source > Generate Getters and Setters...。对话框打开时,单击 Select All,如图 6 所示。

图 6. 生成 getter 和 setter 的 Eclipse
生成 getter 和 setter 的 Eclipse 屏幕截图
生成 getter 和 setter 的 Eclipse 屏幕截图

对于插入点,请选择 Last member 并单击 OK

现在,将一个构造方法添加到 Person 中,方法是将清单 5 中的代码键入到您的源代码窗口中,放在类定义顶部部分下方(public class Person () 下方紧接的一行)。

清单 5. Person 构造方法
public Person(String name, int age, int height, int weight, String eyeColor, String gender) {
  this.name = name;
  this.age = age;
  this.height = height;
  this.weight = weight;
  this.eyeColor = eyeColor;
  this.gender = gender;
}

确保没有看到表示编译错误的波浪线。

生成一个 JUnit 测试案例

现在生成一个 JUnit 测试案例,在该案例中,使用了清单 5 中的构造方法来实例化一个 Person,然后将该对象的状态输出到控制台。从这种意义上讲,“测试”可确保构造方法调用采用的属性顺序是正确的(也就是说,为它们设置了正确的属性)。

在 Package Explorer 中,右键单击您的 Person 类,然后单击 New > JUnit Test Case。New JUnit Test Case 向导的第一页将会打开,如图 7 所示。

图 7. 创建一个 JUnit 测试案例
创建 JUnit 测试案例的第一个对话框的屏幕截图
创建 JUnit 测试案例的第一个对话框的屏幕截图

单击 Next 接受默认值。您会看到 Test Methods 对话框,如图 8 所示。

图 8. 选择方法(向导将为其生成测试案例)
创建 JUnit 测试案例的 Test Methods 对话框的屏幕截图
创建 JUnit 测试案例的 Test Methods 对话框的屏幕截图

在这个对话框中,选择您想让向导为其构建测试的一个或多个方法。在本例中,仅选择构造方法,如图 8 所示。单击 Finish,Eclipse 会生成 JUnit 测试案例。

接下来,打开 PersonTest,进入 testPerson() 方法,使它看起来类似于清单 6。

清单 6. testPerson() 方法
@Test
public void testPerson() {
  Person p = new Person("Joe Q Author", 42, 173, 82, "Brown", "MALE");
  Logger l = Logger.getLogger(Person.class.getName());
  l.info("Name: " + p.getName());
  l.info("Age:"+ p.getAge());
  l.info("Height (cm):"+ p.getHeight());
  l.info("Weight (kg):"+ p.getWeight());
  l.info("Eye Color:"+ p.getEyeColor());
  l.info("Gender:"+ p.getGender());
  assertEquals("Joe Q Author", p.getName());
  assertEquals(42, p.getAge());
  assertEquals(173, p.getHeight());
  assertEquals(82, p.getWeight());
  assertEquals("Brown", p.getEyeColor());
  assertEquals("MALE", p.getGender());
}

暂时不用担心 Logger 类。只需输入您在清单 6 中看到的代码即可。您现在已准备好运行您的第一个 Java 程序和 JUnit 测试案例。

在 Eclipse 中运行单元测试

在 Eclipse 中,右键单击 Package Explore 中的 PersonTest.java 并选择 Run As > JUnit Test。图 9 显示了发生的情况。

图 9. 查看 Person 的运行状况
将 Person 作为 Java 应用程序运行的 Eclipse 的屏幕截图
将 Person 作为 Java 应用程序运行的 Eclipse 的屏幕截图

控制台视图将会自动打开,以显示 Logger 输出,JUnit 视图表明测试运行正常。

向 Java 类添加行为

Person 目前看起来还不错,但可以使用一些额外的行为让它变得更有趣。创建行为意味着添加方法。本节会更详细地介绍访问器方法— 即您已看到过的 getter 和 setter。

访问器方法

您在上一节末尾处实际看到的 getter 和 setter 称为访问器方法。(快速回顾:getter 是检索属性值的方法;setter 是修改该值的方法。)要封装一个类中来自其他对象的数据,可以将它的变量声明为 private,然后提供访问器方法。

访问器的命名遵循称为 JavaBeans 模式的严格约定。在此模式中,任何 Foo 属性都有一个称为 getFoo() 的 getter 方法和一个称为 setFoo() 的 setter 方法。JavaBeans 模式很常见,所以 Eclipse IDE 中内置了对它的支持,您在生成 Person 的 getter 和 setter 时已看到。

访问器遵循以下准则:

  • 属性始终使用 private 访问级别来声明。
  • getter 和 setter 的访问说明符是 public
  • getter 不接受任何参数,而且返回一个与它访问的属性同类型的值。
  • setter 仅接受一个与该属性同类型的参数,而且不返回值。

声明访问器

目前为止,声明访问器的最简单方法是让 Eclipse 为您声明它。但您还需要知道如何手动编写一个 getter 和 setter 对。

假设我有一个属性 Foo,它的类型为 java.lang.String。我的完整 Foo 声明(依照访问器准则)是:

private String foo;
public String getFoo() {
  return foo;
}
public void setFoo(String value) {
  foo = value;
}

请注意,传递给 setter 的参数值的命名方式与通过 Eclipse 生成它时所用的命名方式不同(其中参数名称与属性名称相同 —例如 public void setFoo(String foo))。在我手动编写 setter 的极少数情况下,我始终使用 value 作为 setter 的参数值名称。这个吸引眼球的词汇(我自己的规范,建议其他开发人员也这么做)提醒我,我已经手动编写了 setter。如果我没有使用 Eclipse 为我生成 getter 和 setter,那么我一定拥有合理的理由。使用 value 作为 setter 的参数值,这会提醒我这个 setter 很特殊。(代码注释可满足同样的用途。)

调用方法

调用方法很简单。例如,清单 6 中的 testPerson 方法调用 Person 的各种 getter 来返回它们的值。现在,我将正式介绍执行方法调用的机制。

包含参数和不含参数的方法调用

要在对象上调用某个方法,需要使用对该对象的引用。方法调用语法包含:

  • 对象引用
  • 一个句点
  • 方法名称
  • 所有需要传递的参数

不含参数的方法调用的语法为:

objectReference.someMethod();

以下是一个示例:

Person p = new Person("Joe Q Author", 42, 173, 82, "Brown", "MALE");
p.getName();

包含参数的方法调用的语法为:

objectReference.someOtherMethod(parameter1, parameter2, ..., parameterN);

以下是一个示例(设置 PersonName 属性):

Person p = new Person("Joe Q Author", 42, 173, 82, "Brown", "MALE");
p.setName("Jane Q Author");

请记住,构造方法也是方法。而且您可使用空格和换行符来分隔参数。Java 编译器并不关心分隔方法。下面的两个方法调用是等效的:

new Person("Joe Q Author", 42, 173, 82, "Brown", "MALE");
new Person("Joe Q Author",// Name
  42,     // Age
  173,    // Height in cm
  82,     // Weight in kg
  "Brown",// Eye Color
  "MALE");// Gender

请注意,第二个构造方法调用中的注释使得下一个使用此代码的人更容易理解它。该开发人员一眼就能判断出每个参数的用途。

嵌套的方法调用

方法调用也可以嵌套:

Logger l = Logger.getLogger(Person.class.getName());
l.info("Name: " + p.getName());

在这里,您将 Person.class.getName() 的返回值传递给 getLogger() 方法。请记住,getLogger() 方法调用是一个静态方法调用,所以它的语法稍微不同。(您不需要一个 Logger 引用来执行该调用;而是可以在调用的左侧使用类的名称。)

这就是方法调用。

字符串和运算符

目前为止,教程中介绍了许多 String 类型的变量,但没有进行太多解释。您在这一节中会进一步了解字符串,还会了解何时和如何使用运算符。

字符串

在 Java 语言中,字符串是 String 类型的一级对象,包含一些可帮助您处理它们的方法。

以下是两种创建字符串的方法,我们使用创建一个名为 greeting 且值为 helloString 实例为例:

greeting = new String("hello");
String greeting = "hello";

因为 String 是一级对象,所以可使用 new 实例化它们。将一个 String 类型的变量设置为字符串字面常量具有相同的结果,因为 Java 语言会创建一个 String 对象来保存字面常量,然后将该对象分配给该实例变量。

串联字符串

可以使用 String 做许多事情,该类有许多很有帮助的方法。甚至还没有使用方法,您就已经通过串联(或组合)两个 String,在 Person 类的 testPerson() 方法中做了一些有趣的事情:

l.info("Name: " + p.getName());

加号 (+) 在 Java 语言中是串联 String 的简单表示。(在循环内执行这种串联会影响性能,但目前已不需要担心这一点。)

串联练习

现在,可以尝试在 Person 类内串联两个 String 字符串。此刻您有一个 name 实例变量,但有一个 firstNamelastName 会让应用程序更接近真实的业务应用程序。然后,您可以在另一个对象请求 Person 的全名时串联它们。

返回到 Eclipse 项目,首先添加新实例变量(放在源代码中目前定义 name 的相同位置):

//private String name;
private String firstName;
private String lastName;

注释掉 name 定义;您不再需要它,因为已将它替换为 firstNamelastName

链式方法调用

现在,告诉 Eclipse 代码生成器生成 firstNamelastName 的 getter 和 setter(如有必要,请参考前面的“您的第一个 Java 类”部分)。然后删除 setName()getName() 方法,添加一个新的 getFullName() 方法,使代码类似于:

public String getFullName() {
  return getFirstName().concat(" ").concat(getLastName());
}

此代码演示了方法调用的链接。链接是一种经常用于不可变对象(比如 String)的技术,在链接中,修改一个不可变对象总是返回修改后的对象(但不会更改原始对象)。然后,您可以在返回的已更改的值上进行操作。

运算符

您已看到,Java 语言使用 = 运算符将值赋给变量。正如您可能期望的,Java 语言可执行算术运算,而且它也使用运算符实现此目的。现在,我将简要介绍一下随着您技能的提升需要掌握的一些 Java 语言运算符。

Java 语言使用两种类型的运算符:

  • 一元 (unary):只需一个操作数。
  • 二元 (binary):需要两个操作数。

表 2 总结了 Java 语言的算术运算符:

表 2. Java 语言的算术运算符
运算符用法说明
+a + bab 相加
++a如果 abyteshortchar,则将它升级为 int
-a - ba 中减去 b
--aa 的算术负数
*a * bab 相乘
/a / ba 除以 b
%a % b返回将 a 除以 b 的余数(取模运算符)
++a++a 递增 1;计算递增之前 a 的值
++++aa 递增 1;计算递增之后 a 的值
--a--a 递减 1;计算递减之前 a 的值
----aa 递减 1;计算递减之后 a 的值
+=a += ba = a + b 的简写
-=a -= ba = a - b 的简写
*=a *= ba = a * b 的简写
%=a %= ba = a % b 的简写
其他运算符

除了表 2 中的运算符之外,还可以看到其他一些在 Java 语言中称为运算符的符号,包括:

  • 句点 (.),它限定了包的名称并调用方法
  • 圆括号 (()),它限定了一个方法的逗号分隔的参数列表
  • new,(后跟一个构造方法名称时)它实例化了一个对象

Java 语言语法也包含一些专门用于条件编程的运算符—即根据不同输入提供不同响应的程序。下一节将介绍这些运算符。

条件运算符和控制语句

在本节中,将了解可用来告诉 Java 程序,您希望它们根据不同的输入来执行何种操作的各种语句和运算符。

关系和条件运算符

Java 语言提供了可在代码中用来制定决策的运算符和控制语句。代码中的决策常常以一个 Boolean 表达式开始 — 也即一个计算为 truefalse 的表达式。这些表达式使用关系运算符(将一个操作数或表达式与另一个比较)和条件运算符

表 3 列出了 Java 语言的关系和条件运算符。

表 3. 关系和条件运算符
运算符用法返回 true 的条件……
>a > ba 大于 b
>=a >= ba 大于或等于 b
<a < ba 小于 b
<=a <= ba 小于或等于 b
==a == ba 等于 b
!=a != ba 不等于 b
&&a && b如果 ab 都为 true,则有条件地计算 b(如果 a 为 false,则不计算 b
||a || b如果 ab 为 true,则有条件地计算 b(如果 a 为 true,则不计算 b
!aa 为 false
&a & b如果 ab 都为 true,则始终计算 b
|a | b如果 ab 为 true,则始终计算 b
^a ^ bab 不同

if 语句

有了大量运算符后,是时候使用它们了。下面这段代码展示了在将一种逻辑添加到 Person 对象的 getHeight() 访问器时发生的情况:

public int getHeight() {
  int ret = height;
  // If locale of the computer this code is running on is U.S.,
  if (Locale.getDefault().equals(Locale.US))
    ret /= 2.54;// convert from cm to inches
  return ret;
}

如果当前地区是在美国(未使用公制),将 height(以厘米为单位)的内部值转换为英寸可能更有意义。这个(人为设计的)示例演示了 if 语句的使用,该语句计算圆括号中的一个 Boolean 表达式。如果该表达式计算为 true,则程序执行下一个语句。

在本例中,只有在运行代码的计算机的 LocaleLocale.US 时,才需要执行一条语句。如果需要执行多条语句,可使用花括号来形成一条复合语句。复合语句将许多语句组合成一条语句 —而且复合语句也可包含其他复合语句。

变量范围

Java 应用程序中的每个变量都拥有范围或本地化的名称空间,在该范围内,可以在代码中通过名称访问该变量。在该空间外,该变量就位于范围之外,如果尝试访问它,则会获得编译错误。Java 语言中的范围级别由声明变量的位置来定义,如清单 7 所示。

清单 7. 变量范围
public class SomeClass {
  private String someClassVariable;
  public void someMethod(String someParameter) {
    String someLocalVariable = "Hello";

    if (true) {
      String someOtherLocalVariable = "Howdy";
    }
    someClassVariable = someParameter; // legal
    someLocalVariable = someClassVariable; // also legal
    someOtherLocalVariable = someLocalVariable;// Variable out of scope! 
  }
  public void someOtherMethod() {
    someLocalVariable = "Hello there";// That variable is out of scope! 

  }
}

SomeClass 中,所有实例(即非静态)方法都可以访问 someClassVariablesomeParametersomeMethod 内是可见的,但在该方法外不可见,someLocalVariable 也是如此。在 if 代码块内,声明了 someOtherLocalVariable,在 if 代码块外,它就超出了范围。出于这个原因,我们可以说 Java 拥有代码块范围,因为代码块(由 {} 限定)定义了范围边界。

范围有许多规则,清单 7 给出了最常用的规则。请花几分钟熟悉一下它们。

else 语句

有时在程序的控制流中,您希望仅在一个特定表达式未计算为 true 时执行操作。这时使用 else 将很方便:

public int getHeight() {
  int ret;
  if (gender.equals("MALE"))
    ret = height + 2;
  else {
    ret = height;
    Logger.getLogger("Person").info("Being honest about height...");
  }
  return ret;
}

else 语句的原理与 if 相同,因为程序仅执行它遇到的下一条语句。在本例中,两条语句分组到一个复合语句中(请注意花括号),然后由程序执行。

也可以使用 else 执行一次额外的 if 检查:

if (conditional) {
  // Block 1
} else if (conditional2) {
  // Block 2
} else if (conditional3) {
  // Block 3
} else {
  // Block 4
} // End

如果 conditional 计算为 true,则执行 Block 1,程序跳到最后一个花括号(通过 // End 表示)之后的语句。如果 conditional计算为 true,则计算 conditional2。如果 conditional2 为 true,则计算 Block 2,并且程序跳到最后一个花括号之后的语句。如果 conditional2 不为 true,则程序前进到 conditional3,依此类推。只有在所有 3 个条件都失败时,才执行 Block 4

三元运算符

Java 语言提供了一个方便的运算符来执行简单的 if / else 语句检查。这个运算符的语法是:

(conditional) ? statementIfTrue : statementIfFalse;

如果 conditional 计算为 true,则执行 statementIfTrue;否则执行 statementIfFalse。每条语句都不允许使用复合语句形式。

在获知如果条件计算为 true,需要执行一条语句,如果不为 true,则执行另一条语句时,使用三元运算符很方便。三元运算符通常用于初始化一个变量(比如一个返回值),类似这样:

public int getHeight() {
  return (gender.equals("MALE")) ? (height + 2) : height;
}

问号后面的圆括号不是必需的,但它们使代码变得更可容易理解。

循环

除了能够对程序应用各种条件之外,还可以根据各种 if/then 场景得到不同的结果,您有时还想让代码反复执行同一件事,直到作业完成。在本节中,将了解用于迭代代码或多次执行代码的构造:

什么是循环?

循环是一种编程构造,它在满足某个特定条件(或某组条件)时反复执行。例如,您可能要求程序读取数据文件中的所有记录,或者依次处理数组中的每个元素。(下一节将介绍数组。)

3 种循环构造使迭代代码或多次执行代码成为可能:

  • for 循环
  • while 循环
  • do...while 循环

for 循环

Java 语言中的基本循环构造是 for 语句。可以使用 for 语句迭代一个值范围,以确定执行多少次循环。for 循环的抽象语法是:

for (initialization; loopWhileTrue; executeAtBottomOfEachLoop) {
  statementsToExecute
}

在循环的开头,执行初始化语句(多个初始化语句可使用逗号分开)。 只要 loopWhileTrue(一个必须计算为 truefalse 的 Java 条件表达式)为 true,就会执行该循环。在循环的底部,执行 executeAtBottomOfEachLoop

例如,如果想让清单 8 中的 main() 方法中的代码执行 3 次,可使用一个 for 循环。

清单 8. 一个 for 循环
public static void main(String[] args) {
  Logger l = Logger.getLogger(Person.class.getName());
  for (int aa = 0; aa < 3; aa++) 
    Person p = new Person("Joe Q Author", 42, 173, 82, "Brown", "MALE");
    l.info("Loop executing iteration# " + aa);
    l.info("Name: " + p.getName());
    l.info("Age:"+ p.getAge());
    l.info("Height (cm):"+ p.getHeight());
    l.info("Weight (kg):"+ p.getWeight());
    l.info("Eye Color:"+ p.getEyeColor());
    l.info("Gender:"+ p.getGender());
  }
}

在清单 8 的开头,局部变量 aa 初始化为 0。此语句仅在初始化该循环时执行一次。然后,该循环继续执行 3 次,每次都将 aa 递增 1。

在下一节将会看到,可使用一种替代性的 for 循环语法来循环执行实现 Iterable 接口的构造(比如数组和其他 Java 实用程序类)。就现在而言,只需注意清单 8 中的 for 循环语法的用法。

while 循环

while 循环的语法是:

while (condition) {
  statementsToExecute
}

您可能已经猜到,如果 condition 计算为 true,则执行该循环。在每个迭代的顶部(即执行任何语句之前),计算该条件。如果该条件计算为 true,则执行循环。因此,如果一个 while 循环的条件表达式至少有一次未计算为 true,则不能执行该循环。

再次看看清单 8 中的 for 循环。作为对比,清单 9 使用了一个 while 循环来获取同样的结果。

清单 9. 一个 while 循环
public static void main(String[] args) {
  Logger l = Logger.getLogger(Person.class.getName());
  int aa = 0;
  while (aa < 3) {
    Person p = new Person("Joe Q Author", 42, 173, 82, "Brown", "MALE");
    l.info("Loop executing iteration# " + aa);
    l.info("Name: " + p.getName());
    l.info("Age:"+ p.getAge());
    l.info("Height (cm):"+ p.getHeight());
    l.info("Weight (kg):"+ p.getWeight());
    l.info("Eye Color:"+ p.getEyeColor());
    l.info("Gender:"+ p.getGender());
    aa++;
  }
}

如您所见,while 循环需要做比 for 循环更多的工作。您必须初始化 aa 变量,还要记得在循环底部递增它。

do...while 循环

如果想要一个总是执行一次然后就检查它的条件表达式的循环,可以使用 do...while 循环,如清单 10 所示。

清单 10. 一个 do...while 循环
int aa = 0;
do {
  Person p = new Person("Joe Q Author", 42, 173, 82, "Brown", "MALE");
  l.info("Loop executing iteration# " + aa);
  l.info("Name: " + p.getName());
  l.info("Age:"+ p.getAge());
  l.info("Height (cm):"+ p.getHeight());
  l.info("Weight (kg):"+ p.getWeight());
  l.info("Eye Color:"+ p.getEyeColor());
  l.info("Gender:"+ p.getGender());
  aa++;
} while (aa < 3);

条件表达式 (aa < 3) 在循环结束前不会执行检查。

循环终止

有时,需要在条件表达式计算为 false 之前跳出(或终止)循环。如果在一个 String 数组中搜索某个特定值,而且只想找到它,并不关心数组的其他元素,则可能出现这种情况。对于想要跳出循环的情况,Java 语言提供了 break 语句,如清单 11 所示。

清单 11. 一条 break 语句
public static void main(String[] args) {
  Logger l = Logger.getLogger(Person.class.getName());
  int aa = 0;
  while (aa < 3) {
    if (aa == 1)
      break;
    Person p = new Person("Joe Q Author", 42, 173, 82, "Brown", "MALE");
    l.info("Loop executing iteration# " + aa);
    l.info("Name: " + p.getName());
    l.info("Age:"+ p.getAge());
    l.info("Height (cm):"+ p.getHeight());
    l.info("Weight (kg):"+ p.getWeight());
    l.info("Eye Color:"+ p.getEyeColor());
    l.info("Gender:"+ p.getGender());
    aa++;
  }
}

break 语句跳转到它所在循环外的下一条可执行语句。

循环继续

清单 11 中的(简化)示例中,您只想执行循环一次并跳出。也可以只跳过循环的一次迭代,但继续执行下一次循环。要实现此目标,需要使用 continue 语句,如清单 12 所示。

清单 12. 一条 continue 语句
public static void main(String[] args) {
  Logger l = Logger.getLogger(Person.class.getName());
  int aa = 0;
  while (aa < 3) {
    aa++;
    if (aa == 2)
      continue;
    Person p = new Person("Joe Q Author", 42, 173, 82, "Brown", "MALE");
    l.info("Loop executing iteration# " + aa);
    l.info("Name: " + p.getName());
    l.info("Age:"+ p.getAge());
    l.info("Height (cm):"+ p.getHeight());
    l.info("Weight (kg):"+ p.getWeight());
    l.info("Eye Color:"+ p.getEyeColor());
    l.info("Gender:"+
    p.getGender());
  }
}

在清单 12 中,跳过了循环的第二次迭代,但继续执行第三次迭代。例如,在处理记录并遇到一条您完全不想处理的记录时,使用 continue 就很方便。您可以跳过该记录并前进到下一条记录。

Java 集合

大多数真实应用程序都会处理像文件、变量、来自文件的记录或数据库结果集这样的集合。Java 语言有一个复杂的集合框架,可以使用它创建和管理各种类型的对象集合。本节将介绍最常用的集合类并帮助您开始使用它们。

数组

大多数编程语言都包含数组的概念,用数组来保存一组元素,Java 语言也不例外。数组基本来讲是一个相同类型的元素的集合。

可以通过两种方式声明数组:

  • 创建一个具有一定大小的数组,这个大小在数组的整个生存期中是固定的。
  • 创建一个具有一组初始值的数组。这个集合的大小决定了数组的大小 —它的大小恰好够容纳所有这些值,而且它的大小在数组的整个生存期中是固定的。

声明一个数组

一般而言,可以像这样声明一个数组:

new elementType [arraySize]

可以通过两种方式创建一个整数元素数组。这条语句将创建一个拥有 5 个元素的空间的数组,但它是空的:

// creates an empty array of 5 elements:
int[] integers = new int[5];

这条语句创建该数组并一次性初始化它:

// creates an array of 5 elements with values:
int[] integers = new int[] { 1, 2, 3, 4, 5 };

// creates an array of 5 elements with values (without the new operator):
int[] integers = { 1, 2, 3, 4, 5 };

初始值放在花括号内,用逗号分隔。

另一种创建数组的方法是先创建它,然后编写一个循环来初始化它:

int[] integers = new int[5];
for (int aa = 0; aa < integers.length; aa++) {
  integers[aa] = aa+1;
}

前面的代码声明了一个可容纳 5 个元素的整数数组。如果尝试在该数组中放入多于 5 个元素,Java 运行时将会抛出一个异常。您将在第 2 部分中了解异常和如何处理它们。

加载数组

要加载数组,需要循环从 1 一直到数组长度的整数(可以在数组上调用 .length 来获取它的长度— 稍后会更详细地介绍相关内容)。在本例中,循环在到达 5 时停止。

加载数组后,可以像之前一样访问它:

Logger l = Logger.getLogger("Test");
for (int aa = 0; aa < integers.length; aa++) {
  l.info("This little integer's value is: " + integers[aa]);
}

此语法也是有效的,而且(因为它更容易使用)本节将全部使用此语法:

Logger l = Logger.getLogger("Test");
for (int i : integers) {
  l.info("This little integer's value is: " + i);
}

元素索引

可将数组想成一系列的桶,每个桶中放入一个某种类型的元素。每个桶通过一个元素索引来访问:

element = arrayName [elementIndex];

要访问一个元素,需要数组的引用(它的名称)和包含您想要访问的元素的索引。

length 属性

每个数组都有一个 length 属性,该属性具有 public 可视性,可用于确定可将多少个元素放入该数组中。要访问此属性,可使用数组引用、一个句点 (.) 和关键字 length,就象这样:

int arraySize = arrayName.length;

Java 语言中的数组从 0 开始建立索引。也就是说,对于所有数组,数组中的第一个元素始终位于 arrayName[0],最后一个元素位于 arrayName[arrayName.length - 1]

一个对象数组

您已看到数组如何容纳原语类型,但值得一提的是,它们也可以容纳对象。创建一个 java.lang.Integer 对象数组与创建一个原语类型数组没有太多区别,可通过两种方式来创建它:

// creates an empty array of 5 elements: 
Integer[] integers = new Integer[5];
// creates an array of 5 elements with values: 
Integer[] integers = new Integer[] {
Integer.valueOf(1),
Integer.valueOf(2),
Integer.valueOf(3),
Integer.valueOf(4),
Integer.valueOf(5)
};

装箱和拆箱

Java 语言中的每种原语类型都有一个对应的 JDK 类,如表 4 所示。

表 4. 原语和对应的 JDK 类
原语对应的 JDK 类
booleanjava.lang.Boolean
bytejava.lang.Byte
charjava.lang.Character
shortjava.lang.Short
intjava.lang.Integer
longjava.lang.Long
floatjava.lang.Float
doublejava.lang.Double

每个 JDK 类都提供了相应方法来解析它的内部表示,并将其转换为相应的原语类型。例如,下面这段代码将十进制值 238 转换为一个 Integer

int value = 238;
Integer boxedValue = Integer.valueOf(value);

此技术称为装箱,因为您将原语放在一个包装器或箱子中。

类似地,要将 Integer 表示转换为对应的 int 类,可以对它进行拆箱

Integer boxedValue = Integer.valueOf(238);
int intValue = boxedValue.intValue();

自动装箱和自动拆箱

严格来讲,不需要显式对原语进行装箱和拆箱。 可以使用 Java 语言的自动装箱和自动拆箱特性:

int intValue = 238;

Integer boxedValue = intValue;
//
intValue = boxedValue;

但是,建议避免使用自动装箱和自动拆箱,因为它们可能导致代码可读性问题。装箱和拆箱代码段中的代码比自动装箱的代码更一目了然,因此更容易阅读;为此投入额外的工作是值得的。

解析和转换装箱的类型

您已经了解了如何获取一个装箱的类型,但如何才能将一个您怀疑拥有装箱类型的数字 String 解析到它的正确箱子中呢?JDK 包装器类也拥有实现此目标的方法:

String characterNumeric = "238";
Integer convertedValue = Integer.parseInt(characterNumeric);

还可以将 JDK 包装器类型的内容转换为 String

Integer boxedValue = Integer.valueOf(238);
String characterNumeric = boxedValue.toString();

请注意,在 String 表达式中使用串联运算符时(您已在对 Logger 的调用中看到过),原语类型已自动装箱,而且包装器类型会自动在它们之上调用 toString()。非常方便。

List

List 是一种有序集合,也称为序列。因为 List 是有序的,所以您能够完全控制将列表项放入 List 中的何处。Java List 集合只能包含对象(不能包含像 int 这样的原语类型),而且它为其行为方式定义了严格的契约。

List 是一个接口,所以不能直接将其实例化。(您将在第 2 部分中了解接口。)这里将使用它的最常用实现 ArrayList。可通过两种方式声明它。第一种方式使用了显式语法:

List<String> listOfStrings = new ArrayList<String>();

第二种方式使用了“菱形”运算符(在 JDK 7 中引入):

List<String> listOfStrings = new ArrayList<>();

请注意,ArrayList 实例化过程中没有指定对象类型。这是因为表达式右侧的类的类型必须与左侧的类型匹配。在本教程的剩余部分中,两种类型都会用到,因为您可能在实践中看到这两种用法。

请注意,我将 ArrayList 对象赋给了一个 List 类型的变量。在 Java 编程中,可以将一种类型的变量赋给另一种类型的变量,只要被赋值的变量是赋值变量所实现的超类或接口。在后面的一节中,将进一步介绍这些变量赋值类型的约束规则。

正式类型

前面的代码段中的 <Object> 称为正式类型<Object> 告诉编译器,这个 List 包含一个 Object 类型的集合,这意味着您可以将喜欢的任何实体放在 List 中。

如果您想对能或不能放入 List 中的实体施加更严格的限制,可通过不同方式定义该正式类型:

List<Person> listOfPersons = new ArrayList<Person>();

现在,您的 List 只能包含 Person 实例。

使用 List

使用 List— 通常像使用 Java 集合一样— 非常容易。以下是可对 List 执行的一些操作:

  • List 中放入列表项。
  • 询问 List 目前有多大。
  • List 中获取列表项。

要将列表项放在 List 中,请调用 add() 方法:

List<Integer> listOfIntegers = new ArrayList<>();
listOfIntegers.add(Integer.valueOf(238));

add() 方法将元素添加到 List 的末尾。

要询问 List 有多大,请调用 size()

List<Integer> listOfIntegers = new ArrayList<>();

listOfIntegers.add(Integer.valueOf(238));
Logger l = Logger.getLogger("Test");
l.info("Current List size: " + listOfIntegers.size());

要从 List 中检索某个项,请调用 get() 并向它传递您想要的项的索引:

List<Integer> listOfIntegers = new ArrayList<>();
listOfIntegers.add(Integer.valueOf(238));
Logger l = Logger.getLogger("Test");
l.info("Item at index 0 is: " listOfIntegers.get(0));

在真实的应用程序中,List 将包含记录或业务对象,您可能想要在处理过程中查看所有这些对象。如何以通用方式实现该目标?答案:您想要迭代该集合,可以这么做是因为 List 实现了 java.lang.Iterable 接口。

Iterable

如果一个集合实现了 java.lang.Iterable,那么它称为迭代变量集合。您可从一端开始,逐项地处理集合,直到处理完所有项。

在“循环”部分,我简要提及了对实现 Iterable 接口的集合进行迭代的特殊语法。这里将更详细地介绍该语法:

for (objectType varName : collectionReference) {
  // Start using objectType (via varName) right away...
}

之前的代码比较抽象,这是一个更切合实际的示例:

List<Integer> listOfIntegers = obtainSomehow();
Logger l = Logger.getLogger("Test");
for (Integer i : listOfIntegers) {
  l.info("Integer value is : " + i);
}

这个小代码段所做的事情与下面这个长代码段所做事情相同:

List<Integer> listOfIntegers = obtainSomehow();
Logger l = Logger.getLogger("Test");
for (int aa = 0; aa < listOfIntegers.size(); aa++) {
  Integer I = listOfIntegers.get(aa);
  l.info("Integer value is : " + i);
}

第一个代码段使用了简写语法:它没有 index 变量(在本例中为 aa)要初始化,也没有调用 Listget() 方法。

因为 List 扩展了 java.util.Collection(它实现了 可迭代),所以可以使用简写语法来迭代任何 List

Set

Set 是一种集合构造,根据定义,它包含唯一的元素— 即没有重复项。List 可包含同一个对象数百次,而 Set 只能包含某个特定实例一次。Java Set 集合只能包含对象,而且它为其行为方式定义了严格的契约。

因为 Set 是一个接口,所以您不能直接将其实例化。我最喜欢的实现之一是 HashSet,它易于使用且类似于 List

以下是可对 Set 执行的一些操作:

  • Set 中放入内容。
  • 询问 Set 目前有多大。
  • Set 中获取内容。

Set 的一个独特特征是,它可保证其元素的唯一性,但不关心元素的顺序。考虑以下代码:

Set<Integer> setOfIntegers = new HashSet<Integer>();
setOfIntegers.add(Integer.valueOf(10));
setOfIntegers.add(Integer.valueOf(11));
setOfIntegers.add(Integer.valueOf(10));
for (Integer i : setOfIntegers) {
  l.info("Integer value is: " + i);
}

您可能认为该 Set 中有 3 个元素,但它仅有两个,因为包含值 10Integer 对象仅添加了一次。

请在迭代 Set 时注意此行为,如下所示:

Set<Integer> setOfIntegers = new HashSet();
setOfIntegers.add(Integer.valueOf(10));
setOfIntegers.add(Integer.valueOf(20));
setOfIntegers.add(Integer.valueOf(30));
setOfIntegers.add(Integer.valueOf(40));
setOfIntegers.add(Integer.valueOf(50));
Logger l = Logger.getLogger("Test");
for (Integer i : setOfIntegers) {
  l.info("Integer value is : " + i);
}

对象的输出顺序可能与添加顺序不同,因为 Set 保证了唯一性,但不保证顺序。如果将前面的代码粘贴到 Person 类的 main() 方法中并运行它,将可以看到这个结果。

Map

Map 是一种方便的集合构造,可以使用它将一个对象()与另一个对象()相关联。可以想象到,Map 的键必须是唯一的,而且可在以后使用它来检索值。Java Map 集合只能包含对象,而且它为其行为方式定义了严格的契约。

因为 Map 是一个接口,所以不能直接将其实例化。我最喜欢的实现之一是 HashMap

可对 Map 执行的操作包括:

  • Map 中放入内容。
  • Map 中获取内容。
  • 获取 Map 的键 Set— 用于迭代它。

要在 Map 中放入内容,需要拥有一个表示它的键的对象和一个表示它的值的对象:

public Map<String, Integer> createMapOfIntegers() {
  Map<String, Integer> mapOfIntegers = new HashMap<>();
  mapOfIntegers.put("1", Integer.valueOf(1));
  mapOfIntegers.put("2", Integer.valueOf(2));
  mapOfIntegers.put("3", Integer.valueOf(3));
  //...
  mapOfIntegers.put("168", Integer.valueOf(168));
return mapOfIntegers;
}

在这个示例中,Map 包含 Integer,采用一个 String 作为键,这恰好是它们的 String 表示。要检索某个特定的 Integer 值,需要它的 String 表示:

mapOfIntegers = createMapOfIntegers();
Integer oneHundred68 = mapOfIntegers.get("168");

结合使用 Set 和 Map

有时,您可能发现您拥有一个 Map 的引用,而且您想遍历它的整个内容集合。在这种情况下,需要 Map 的键的 Set

Set<String> keys = mapOfIntegers.keySet();
Logger l = Logger.getLogger("Test");
for (String key : keys) {
  Integer  value = mapOfIntegers.get(key);
  l.info("Value keyed by '" + key + "' is '" + value + "'");
}

请注意,在用于 Logger 调用时,会自动调用从 Map 检索的 IntegertoString() 方法。Map 返回它的键的 Set,因为 Map 拥有键,而且每个键是唯一的。 唯一性(而不是顺序)是 Set 的独特特征(这可以解释为什么没有 keyList() 方法)。

归档 Java 代码

现在您已经了解了如何编写 Java 应用程序,您可能想知道如何打包它们,以便其他开发人员可以使用它们,或者您想知道如何将其他开发人员的代码导入到您的应用程序中。本节将展示如何做。

JAR

JDK 附带了一个称为 JAR 的工具,它代表 Java Archive(Java 归档文件)。您可以使用此工具创建 JAR 文件。将代码打包到 JAR 文件中后,其他开发人员可将该 AJR 文件放入其项目中,并配置他们的项目来使用您的代码。

在 Eclipse 中创建 JAR 文件很简单。在您的工作区中,右键单击 com.makotojava.intro 包并单击 File > Export。您会看到如图 10 所示的对话框。选择 Java > JAR file 并单击 Next

图 10. Export 对话框
Eclipse 导出对话框的屏幕截图
Eclipse 导出对话框的屏幕截图

打开下一个对话框时,浏览到您想要存储 JAR 文件的位置,并将该文件命名为您喜欢的名称。.jar 扩展名是默认扩展名,建议使用它。单击 Finish

您会在您选择的位置上看到您的 JAR 文件。如果将该 JAR 放在 Eclipse 中的构建路径中,可以从您的代码中使用该文件中的类。接下来您会看到,这么做也很容易。

使用第三方应用程序

JDK 功能全面,但它无法满足编写良好 Java 代码的所有需求。随着越来越熟悉 Java 应用程序的编写,您可能想使用越来越多的第三方应用程序来支持您的代码。Java 开源社区提供了许多库来帮助填补这些空白。

例如,假设您想要使用 Apache Commons Lang,这是一个处理核心 Java 类的 JDK 替换库。Commons Lang 提供的类可帮助处理数组,创建随机数,并执行字符串操作。

假设您已经下载了 Commons Lang,将它存储在一个 JAR 文件中。要使用这些类,第一步操作是在项目中创建一个 lib 目录并将该 JAR 文件放入其中:

  1. 在 Eclipse Project Explorer 视图中右键单击 Intro 根文件夹。
  2. 单击 New > Folder 并将该文件夹命名为 lib
  3. 单击 Finish

新文件夹显示在与 src 相同的级别上。现在将 Commons Lang JAR 文件复制到您的新 lib 目录中。在本示例中,该文件名为 commons-lang3-3.4.jar。(我们通常会在 JAR 文件的命名中包含版本号,在本例中为 3.4。)

现在您只需告诉 Eclipse 将 commons-lang3-3.4.jar 文件中的类包含到您的项目中:

  1. 在 Package Explorer 中,选择 lib 文件夹,右键单击并选择 Refresh
  2. 确认 lib 文件夹中显示了 JAR: 刷新后的 lib 文件夹的屏幕截图
    刷新后的 lib 文件夹的屏幕截图
  3. 右键单击 commons-lang3-3.4 并选择 Build Path > Add to Build Path

Eclipse 处理 JAR 文件中的代码(即类文件)后,就可以从您的 Java 代码引用(导入)它们。可在 Project Explorer 中注意到,您有一个名为 Referenced Libraries 的新文件夹,其中包含 commons-lang3-3.4.jar 文件。

编写良好的 Java 代码

您已经掌握了编写基本的 Java 程序所需的足够多的 Jave 语法,这意味着本教程的前半部分即将结束。最后一节提供了一些最佳实践,它们可帮助您编写更干净、更容易维护的 Java 代码。

让类保持较小

目前您已创建了一些类。即使仅为少量(根据真实 Java 类的标准)属性生成 getter/setter 对,Person 类也有 150 行代码。这么大的 Person 仍是一个小类。我们常常(而且不幸地)看到包含 50 或 100 个方法和数千行(或更多)源代码的类。一些类可能必须这么大,但很可能它们需要重构。重构指的是更改现有代码的设计,而不更改它的结果。建议遵循以下最佳实践。

一般而言,类表示应用程序中的某个概念实体,类的大小应只反映了执行该实体需要执行的操作的功能。它们应该高度专注于很好地执行少量的操作。

仅保留您需要的方法。如果您需要几个执行相同操作但接受不同参数的帮助器方法(比如 printAudit() 方法),这是一个不错的选择。但请确保将方法列表限制到您需要的水平,不要使用更多的方法。

谨慎地为方法命名

对于方法名称,一种不错的编码模式是意图揭示性方法名称模式。通过一个简单的示例,就可以很容易地理解此模式。只看一眼,以下哪个方法名称更容易理解?

  • a()
  • computeInterest()

答案应该很明显,但出于某种原因,程序员倾向于为方法(在这里还包括变量)提供简短的名称。当然,过长的名称可能不方便,但传达某个方法的用途的名称不需要过长。在编写大量代码 6 个月后,您可能已经不记得某个名为 compInt() 的方法的用途,但名为 computeInterest() 的方法的用途就很明显,该方法可能用于计算利息。

保持方法简短

与小类一样,小方法更受欢迎,而且原因类似。我尝试遵循的一条原则是,将方法的大小限制为一页,因为我可以在我的屏幕上看到它。这种做法使应用程序类更容易维护。

如果一个方法增长到超出一页,我就会重构它。Eclipse 拥有一组非常棒的重构工具。通常,一个长方法包含一些聚集在一起的功能的子组。可将某个功能移到另一个方法中(并相应地命名它),然后根据需要传入参数。

将每个方法限制到单个作业。我发现,一个方法做好一件事通常只需不超过 30 行代码。

重构和编写测试优先代码的能力是新程序员要学习的最重要技能。如果每个人都擅长这两项技能,业界将发生革命性变化。如果您擅长这两项技能,您最终会生成比许多同行更干净的代码和功能更强的应用程序。

使用注释

请使用注释。长期关注您的人(甚至 6 个月后的您自己)会感谢您。您可能听过一句格言如果代码写得好可以自述其身,谁还需要注释?我会给出两个原因来解释为什么我相信此格言是错误的:

  • 大多数代码都没有精心编写。
  • 尽管我们很努力,但我们编写的代码可能并不像我们认为的那么精美。

所以,请给您的代码添加注释。就这么简单。

使用一致的风格

编码风格是一种个人偏好,但我建议使用标准的 Java 括号语法:

public static void main(String[] args) {
}

不要使用此风格:

public static void main(String[] args)
{
}

或这种风格:

public static void main(String[] args)
  {
  }

为什么?因为它是标准的,所以您遇到的大部分代码(比如不是您编写的,但您可能要付费维护的代码)很可能就是以这种方式编写的。Eclipse 的确允许您以任何喜欢的方式定义代码风格和格式化代码。但是,如果您刚接触 Java,可能还没有形成风格。 那么,我建议从一开始就采用 Java 标准。所以我建议从一开始就采用 Java 标准。

使用内置记录

在 Java 1.4 引入内置记录之前,确定程序在执行何种操作的权威方式是执行一次类似这样的系统调用:

public void someMethod() {
  // Do some stuff...
  // Now tell all about it
  System.out.println("Telling you all about it:");
  // Etc...
}

Java 语言的内置记录工具(请参考前面的“您的第一个 Java 类”部分)是一个更好的替代工具。我从不在我的代码中使用 System.out.println(),建议您也不要使用它。另一种替代选择是常用的 log4j 替换库,它是 Apache umbrella 项目的一部分。

第 1 部分小结

在本教程中,您学习了面向对象编程,了解了可用于创建有用对象的 Java 语法,还熟悉了一种可帮助控制开发环境的 IDE。您了解了如何创建和运行可执行大量操作的 Java 对象,包括根据不同的输入来执行不同的操作。您还了解了如何创建应用程序的 JAR,供其他开发人员在其程序中使用,并掌握了一些基本的 Java 编程最佳实践。

后续计划

本教程的后半部分中,将开始学习 Java 编程中的一些更高级的构造,但整体讨论仍是介绍性的。该教程中涵盖的 Java 编程主题包括:

  • 异常处理
  • 继承和抽象
  • 接口
  • 嵌套类
  • 正则表达式
  • 泛型
  • 枚举类型
  • I/O
  • 序列化

参阅“Java 编程简介,第 2 部分:真实应用程序的构造”。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Java technology
ArticleID=1022899
ArticleTitle=Java 编程简介,第 1 部分: Java 语言基础
publish-date=09142017