理解 Java 程序
导航 入门 主题: ) |
本文介绍了一个可以在控制台中运行的小型 Java 程序。它计算平面上的两点之间的距离。您现在不需要理解程序的结构和意义;我们很快就会讲到。此外,由于该程序旨在作为简单的入门介绍,因此它有一些改进空间,并且我们将在本模块后面介绍一些改进措施。但不要操之过急!
此类名为 Distance,因此使用您喜欢的编辑器或 Java IDE,首先创建一个名为 Distance.java
的文件,然后复制下面的源代码,将其粘贴到文件中并保存文件。
代码清单 2.1:Distance.java
public class Distance {
private java.awt.Point point0, point1;
public Distance(int x0, int y0, int x1, int y1) {
point0 = new java.awt.Point(x0, y0);
point1 = new java.awt.Point(x1, y1);
}
public void printDistance() {
System.out.println("Distance between " + point0 + " and " + point1
+ " is " + point0.distance(point1));
}
public static void main(String[] args) {
Distance dist = new Distance(
intValue(args[0]), intValue(args[1]),
intValue(args[2]), intValue(args[3]));
dist.printDistance();
}
private static int intValue(String data) {
return Integer.parseInt(data);
}
}
|
此时,您可能希望查看源代码以了解您能理解多少。虽然它可能不是最易读的编程语言,但了解其他过程式语言(如 C)或其他面向对象语言(如 C++ 或 C#)的人将能够理解大多数(如果不是全部)示例程序。
保存文件后,编译程序
编译命令
$ javac Distance.java |
(如果 javac
命令失败,请查看 安装说明。)
要运行程序,您需要提供平面上的两个点的 x 和 y 坐标,并用空格隔开。对于此版本的 Distance,仅支持整数点。命令序列是 java Distance <x0> <y0> <x1> <y1>
,用于计算点 (x0, y0) 和 (x1, y1) 之间的距离。
如果您收到 java.lang.NumberFormatException 异常,则某些参数不是数字。如果您收到 java.lang.ArrayIndexOutOfBoundsException 异常,则您没有提供足够的数字。 |
以下列出两个示例
点 (0, 3) 和 (4, 0) 之间距离的输出
$ java Distance 0 3 4 0 Distance between java.awt.Point[x=0,y=3] and java.awt.Point[x=4,y=0] is 5.0 |
点 (-4, 5) 和 (11, 19) 之间距离的输出
$ java Distance -4 5 11 19 Distance between java.awt.Point[x=-4,y=5] and java.awt.Point[x=11,y=19] is 20.518284528683193 |
稍后我们将解释这些奇怪的输出,并展示如何改进它。
如我们所承诺,我们现在将提供对该 Java 程序的详细描述。我们将讨论程序的语法和结构,以及该结构的意义。
代码清单
public class Distance {
private java.awt.Point point0, point1;
public Distance(int x0, int y0, int x1, int y1) {
point0 = new java.awt.Point(x0, y0);
point1 = new java.awt.Point(x1, y1);
}
public void printDistance() {
System.out.println("Distance between " + point0 + " and " + point1
+ " is " + point0.distance(point1));
}
public static void main(String[] args) {
Distance dist = new Distance(
intValue(args[0]), intValue(args[1]),
intValue(args[2]), intValue(args[3]));
dist.printDistance();
}
private static int intValue(String data) {
return Integer.parseInt(data);
}
}
|
- 图 2.1: 基本 Java 语法。
- 有关 Java 语法元素的进一步介绍,另请参阅 语法。
Java 类的 语法 是用于编码类的字符、符号及其结构。Java 程序由一系列标记组成。标记有多种类型。例如,有单词标记,如 class
和 public
,它们代表 关键字 (以紫色显示 在上图中)——Java 中具有保留含义的特殊单词。其他单词,如 Distance
、point0
、x1
和 printDistance
,不是关键字,而是 标识符(以灰色显示)。标识符在 Java 中有许多不同的用途,但主要用作名称。Java 还有标记来表示数字,如 1
和 3
;这些被称为 字面量 (以橙色显示)。字符串字面量 (以蓝色显示),如 "Distance between "
,由嵌入双引号中的零个或多个字符组成,而 运算符 (以红色显示),如 +
和 =
,用于表达基本计算,如加法或字符串连接或赋值。还有左大括号和右大括号 ({
和 }
),它们将 块 括起来。类的主体就是一个这样的块。某些标记是标点符号,如句点 .
、逗号 ,
和分号 ;
。您使用 空格(如空格、制表符和换行符)来分隔标记。例如,关键字和标识符之间需要空格:publicstatic
是一个包含十二个字符的单个标识符,而不是两个 Java 关键字。
public class Distance {
private java.awt.Point point0, point1;
public Distance(int x0, int y0, int x1, int y1) {
point0 = new java.awt.Point(x0, y0);
point1 = new java.awt.Point(x1, y1);
}
public void printDistance() {
System.out.println("Distance between " + point0 + " and " + point1
+ " is " + point0.distance(point1));
}
public static void main(String[] args) {
Distance dist = new Distance(
intValue(args[0]), intValue(args[1]),
intValue(args[2]), intValue(args[3]));
dist.printDistance();
}
private static int intValue(String data) {
return Integer.parseInt(data);
}
}
- 图 2.2: 声明和定义。
如上所示,使用一系列标记构建 Java 类下一个构建块:声明和定义。类声明提供类的名称和可见性。在我们的示例中,public class Distance
是类声明。它由(在这种情况下)两个关键字组成,
和 public
,后面跟着标识符 class
Distance
。
这意味着我们正在定义一个名为 Distance
的类。其他类,或者在我们的例子中,命令行,可以通过此名称引用该类。public
关键字是 访问修饰符,它声明该类及其成员可以从其他类访问。class
关键字显然将此声明标识为一个类。Java 还允许声明 接口 和 注解。
类声明后面跟着一个块(用大括号包围),它提供类的定义 (在 图 2.2 中以蓝色显示)。定义是类的实现 - 类成员的声明和定义。该类恰好包含六个成员,我们将依次解释。
- 两个名为
point0
和point1
的字段声明 (以绿色显示) - 一个构造函数声明 (以橙色显示)
- 三个方法声明 (以红色显示)
示例:实例字段
[edit | edit source]声明
代码段 2.1:声明。
private java.awt.Point point0, point1;
|
...声明了两个 实例字段。实例字段代表在构造类的实例时分配的命名值。当 Java 程序创建一个 Distance
实例时,该实例将包含 point0
和 point1
的空间。当创建另一个 Distance
对象时,它将包含其自身的 point0
和 point1
值的空间。第一个 Distance
对象中 point0
的值可以独立于第二个 Distance
对象中 point0
的值而变化。
此声明包括
private
访问修饰符,
这意味着这些实例字段对其他类不可见。- 实例字段的类型。在本例中,类型为
java.awt.Point
。
这是java.awt
包中的类Point
。 - 以逗号分隔的列表中的实例字段的名称。
这两个字段也可以用两个独立但更详细的声明来声明,
代码段 2.2:详细声明。
private java.awt.Point point0;
private java.awt.Point point1;
|
由于这些字段的类型是引用类型(即一个引用或可以保存对对象值的引用的字段),因此当创建 Distance
实例时,Java 将隐式地将 point0
和 point1
的值初始化为 null。null 值意味着引用值不引用任何对象。特殊的 Java 字面量 null
用于在程序中表示 null 值。虽然可以在声明中显式地分配 null 值,如
代码段 2.3:声明和赋值。
private java.awt.Point point0 = null;
private java.awt.Point point1 = null;
|
但这是不必要的,大多数程序员会省略这种默认赋值。
示例:构造函数
[edit | edit source]构造函数 是类中的一种特殊方法,用于构造类的实例。构造函数可以执行对对象的初始化,超出 Java VM 自动执行的初始化。例如,Java 将自动将字段 point0
和 point1
初始化为 null。
代码段 2.4:类的构造函数
public Distance(int x0, int y0, int x1, int y1) {
point0 = new java.awt.Point(x0, y0);
point1 = new java.awt.Point(x1, y1);
}
|
上面的构造函数由五个部分组成
- 可选的 访问修饰符。
在本例中,构造函数被声明为public
- 构造函数名称,必须与类名称完全匹配:在本例中为
Distance
。 - 构造函数参数。
参数列表是必需的。即使构造函数没有任何参数,也必须指定空列表()
。参数列表声明每个方法参数的类型和名称。 - 可选的
throws
子句,它声明构造函数可能引发的 异常。此构造函数没有声明任何异常。 - 构造函数体,它是一个 Java 块(用
{}
括起来)。此构造函数的体包含两个语句。
此构造函数接受四个参数,分别命名为 x0, y0, x1
和 y1
。每个参数都需要一个参数类型声明,在本例中,所有四个参数的类型都是
。参数列表中的参数用逗号分隔。int
此构造函数中的两个赋值使用 Java 的 new
运算符 分配两个 java.awt.Point
对象。第一个分配表示第一个点 (x0, y0)
的对象,并将其分配给 point0
实例变量(替换实例变量初始化的 null 值)。第二个语句分配具有 (x1, y1)
的第二个 java.awt.Point
实例,并将其分配给 point1
实例变量。
这是 Distance 类的构造函数。Distance 隐式地从 java.lang.Object
扩展。如果代码中没有显式地编码,Java 会将对超类构造函数的调用作为构造函数的第一个可执行语句插入。上面的构造函数体等效于以下带有显式超类构造函数调用的体
代码段 2.5:超类构造函数。
{
super();
point0 = new java.awt.Point(x0, y0);
point1 = new java.awt.Point(x1, y1);
}
|
虽然该类确实可以通过其他方式实现,例如仅存储两个点的坐标并计算距离为 ,但该类使用现有的 java.awt.Point
类。此选择与该类的抽象定义相匹配:打印平面上的两点之间的距离。我们利用了 Java 平台中已实现的现有行为,而不是重新实现它。我们将在后面看到如何使程序更灵活,而不会增加太多复杂性,因为我们在这里选择使用对象抽象。但是,关键点是该类使用信息隐藏。也就是说,如何类存储其状态或如何计算距离是隐藏的。我们可以更改此实现,而不会改变客户使用和调用该类的方式。
示例:方法
[edit | edit source]方法 是第三种也是最重要的类成员类型。该类包含三个定义 Distance
类行为的 方法:printDistance()
、main()
和 intValue()
printDistance() 方法
[edit | edit source]printDistance()
方法将两点之间的距离打印到标准输出(通常是控制台)。
代码部分 2.6:printDistance() 方法。
public void printDistance() {
System.out.println("Distance between " + point0
+ " and " + point1
+ " is " + point0.distance(point1));
}
|
这个实例方法在隐式Distance
对象的上下文中执行。实例字段引用point0
和point1
引用该隐式对象的实例字段。您也可以使用特殊变量this
来显式引用当前对象。在实例方法中,Java 将名称this
绑定到执行该方法的对象,并且this
的类型是当前类的类型。printDistance
方法的主体也可以编码为
代码部分 2.7:当前类的显式实例。
System.out.println("Distance between " + this.point0
+ " and " + this.point1
+ " is " + this.point0.distance(this.point1));
|
以使实例字段引用更明确。
此方法在一个语句中计算距离并打印距离。距离使用point0.distance(point1)
计算;distance()
是java.awt.Point
类的实例方法(point0
和point1
是该类的实例)。该方法对point0
进行操作(在方法执行期间将this
绑定到point0
引用的对象),并接受另一个 Point 作为参数。实际上,它比这稍微复杂一点,但我们稍后会解释。distance()
方法的结果是双精度浮点数。
此方法使用语法
代码部分 2.8:字符串连接。
"Distance between " + this.point0
+ " and " + this.point1
+ " is " + this.point0.distance(this.point1)
|
来构建一个字符串,传递给System.out.println()
。此表达式是一系列字符串连接方法,这些方法连接字符串或原始类型(如双精度数)或对象的字符串表示形式,并返回一个长字符串。例如,此表达式针对点 (0,3) 和 (4,0) 的结果是字符串
输出
"Distance between java.awt.Point[x=0,y=3] and java.awt.Point[x=4,y=0] is 5.0" |
该方法随后将其打印到System.out
。
为了打印,我们调用println()
。这是一个来自java.io.PrintStream
的实例方法,它是类java.lang.System
中的静态字段out
的类型。Java VM 在启动程序时将System.out
绑定到标准输出流。
main()
方法
[edit | edit source]main()
方法是 Java 从命令行启动 Java 程序时调用的主入口点。命令
输出
java Distance 0 3 4 0 |
指示 Java 定位 Distance 类,将四个命令行参数放入一个 String 值数组中,然后将这些参数传递给该类的public static main(String[])
方法。我们将在稍后介绍数组。您要从命令行或桌面快捷方式调用的任何 Java 类都必须具有此签名或以下签名的主方法:public static main(String...)
。
代码部分 2.9:main() 方法。
public static void main(String[] args) {
Distance dist = new Distance(
intValue(args[0]), intValue(args[1]),
intValue(args[2]), intValue(args[3]));
dist.printDistance();
}
|
main()
方法调用最终方法intValue()
四次。intValue()
接受单个字符串参数并返回字符串中表示的整数值。例如,intValue("3")
将返回整数 3。
进行测试优先编程或执行回归测试的人会在每个 Java 类中编写一个 main() 方法,并在每个 Python 模块中编写一个main() 函数,以运行自动化测试。当一个人直接执行文件时,main() 方法会执行并运行该文件的自动化测试。当一个人执行另一个 Java 文件,该文件又导入了许多其他 Java 类时,只执行一个 main() 方法 - 直接执行文件的 main() 方法。
intValue()
方法
[edit | edit source]intValue()
方法将它的工作委托给Integer.parseInt()
方法。主方法可以直接调用Integer.parseInt()
;intValue()
方法只是使main()
方法更具可读性。
代码部分 2.10:intValue() 方法。
private static int intValue(String data) {
return Integer.parseInt(data);
}
|
此方法是private
,因为它与字段point0
和point1
一样,是类内部实现的一部分,而不是Distance
类外部编程接口的一部分。
静态方法与实例方法
[edit | edit source]main()
和intValue()
方法都是静态方法。static
关键字告诉编译器创建一个与该类关联的单个内存空间。每个实例化的个体对象都有自己的私有状态变量和方法,但使用与编译器在第一个类对象实例化或创建时创建的单个类对象公用的相同的静态方法和成员。这意味着该方法在静态或非对象上下文中执行 - 当各种对象从各种对象运行静态方法时,没有隐式单独的实例可用,并且特殊变量this
不可使用。因此,静态方法无法直接访问实例方法或实例字段(例如printDistance()
或point0
)。main()
方法只能通过实例引用(例如dist
)调用实例方法printDistance()
方法。
数据类型
[edit | edit source]大多数声明都有一个数据类型。Java 有几种数据类型类别:引用类型、原始类型、数组类型以及一种特殊类型 void。
原始类型
[edit | edit source]原始类型 用于表示布尔值、字符值和数值。此程序只显式地使用一种原始类型,
,它表示 32 位有符号整数值。该程序还隐式地使用int
,这是double
java.awt.Point
的distance()
方法的返回类型。double
值是 64 位 IEEE 浮点数。main()
方法使用整数值 0、1、2 和 3 来访问命令行参数的元素。Distance()
构造函数的四个参数也具有类型int
。此外,intValue()
方法的返回类型为int
。这意味着对该方法的调用,例如intValue(args[0])
,是一个类型为int
的表达式。这有助于解释为什么主方法不能调用
代码部分 2.11:错误类型。
new Distance(args[0], args[1], args[2], args[3]) // This is an error
|
由于args
数组元素的类型为 String,而我们的构造函数参数必须为int
,因此此类调用会导致错误,因为 Java 不会自动将类型为 String 的值转换为int
值。
Java 的原始类型是
、boolean
、byte
、char
、short
、int
、long
和float
。它们都是 Java 语言关键字。double
除了原始类型,Java 还支持引用类型。引用类型是由 Java 类或接口定义的 Java 数据类型。引用类型之所以得名,是因为此类值引用一个对象或包含指向一个对象的引用。这个概念类似于其他语言(如 C)中的指针。
Java 使用引用类型java.lang.String
来表示字符数据序列,或字符串,通常称为String
。字符串字面量(如"Distance between "
)是类型为 String 的常量。
该程序使用三种不同的引用类型
- java.lang.String(或简称为 String)
- Distance
- java.awt.Point
- 有关更多信息,请参阅章节:Java 编程/类、对象和类型。
Java 支持数组,它们是聚合类型,具有固定元素类型(可以是任何 Java 类型)和整型大小。该程序只使用一个数组,String[] args
。这表示args
具有数组类型,且元素类型为String
。Java VM 会构建并初始化传递给main
方法的数组。有关如何创建数组和访问其大小的更多详细信息,请参阅数组。
数组元素使用整型索引进行访问。数组的第一个元素始终是元素 0。该程序使用索引 0、1、2 和 3 显式访问args
数组的前四个元素。该程序没有执行任何输入验证,例如验证用户是否向程序传递了至少四个参数。我们将在后面进行修复。
void
在 Java 中不是类型;它代表类型的缺失。不返回值的方法被声明为void 方法。
该类定义了两个 void 方法
代码部分 2.12:Void 方法
public static void main(String[] args) { ... }
public void printDistance() { ... }
|
Java 中的空白符用于分隔 Java 源文件中的标记。空白符在某些地方是必需的,例如在访问修饰符、类型名称和标识符之间,并在其他地方用于提高可读性。
在 Java 中需要空白符的地方,可以使用一个或多个空白符字符。在 Java 中空白符可选的地方,可以使用零个或多个空白符字符。
Java 空白符由以下内容组成:
- 空格字符
' '
(0x20), - 制表符(十六进制 0x09),
- 换页符(十六进制 0x0c),
- 换行符(十六进制 0x0a)或回车符(十六进制 0x0d)字符。
换行符是特殊的空白符字符,因为它们还会终止行注释,而普通空白符不会。
其他 Unicode 空格字符(包括纵向制表符)在 Java 中不允许用作空白符。
查看static
方法intValue
代码部分 2.13:方法声明
private static int intValue(String data) {
return Integer.parseInt(data);
}
|
在private
和static
之间、static
和int
之间、int
和intValue
之间,以及String
和data
之间都需要空白符。
如果代码这样写
代码部分 2.14:折叠的代码
privatestaticint intValue(String data) {
return Integer.parseInt(data);
}
|
…则意味着完全不同的东西:它声明了一个返回值类型为privatestaticint
的方法。这种类型可能不存在,并且该方法不再是静态的,因此上面的代码会导致语义错误。
Java 会忽略语句前面的所有空白符。因此,这两段代码对于编译器来说是相同的
代码部分 2.15:缩进的代码
public static void main(String[] args) {
Distance dist = new Distance(
intValue(args[0]), intValue(args[1]),
intValue(args[2]), intValue(args[3]));
dist.printDistance();
}
private static int intValue(String data) {
return Integer.parseInt(data);
}
|
代码部分 2.16:未缩进的代码
public static void main(String[] args) {
Distance dist = new Distance(
intValue(args[0]), intValue(args[1]),
intValue(args[2]), intValue(args[3]));
dist.printDistance();
}
private static int intValue(String data) {
return Integer.parseInt(data);
}
|
但是,第一个的风格(带空白符)更受欢迎,因为可读性更高。即使以更高的阅读速度,方法主体也更容易与头部区分开。