理解 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
的字段声明 (用绿色表示) - 一个构造函数声明 (用橙色表示)
- 三个方法声明 (用红色表示)
声明
代码部分 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;
|
由于这些字段的类型是引用类型(即一个 引用 或可以保存 引用 到对象值的字段),Java 会在创建 Distance
实例时隐式地将 point0
和 point1
的值初始化为 null。null 值意味着一个引用值不引用任何对象。特殊的 Java 字面量 null
用于在程序中表示 null 值。虽然你可以在声明中显式地赋值 null 值,就像在
代码部分 2.3: 声明和赋值。
private java.awt.Point point0 = null;
private java.awt.Point point1 = null;
|
中一样,但这不是必需的,大多数程序员会省略这些默认赋值。
一个 构造函数 是类中的一种特殊方法,用于构造类的实例。构造函数可以执行对象的初始化,超出了 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 平台中已有的行为,而不是再次实现它。我们将在后面看到如何使程序更加灵活,而不会增加太多复杂性,因为我们在这里选择使用对象抽象。然而,关键在于这个类使用了信息隐藏。也就是说,如何 存储类的状态或如何 计算距离是隐藏的。我们可以更改此实现,而不会改变客户端使用和调用该类的方式。
方法 是第三种也是最重要的类成员类型。这个类包含三个 方法,其中定义了 Distance
类的行为:printDistance()
、main()
和 intValue()
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
所引用的对象),并接受另一个点作为参数。实际上,它比这稍微复杂一些,但我们将在后面解释。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 虚拟机在启动程序时将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()
方法。main 方法可以直接调用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
类型的表达式。这有助于解释为什么 main 方法不能调用
代码部分 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
引用类型
[edit | edit source]除了基本类型之外,Java 还支持引用类型。引用类型是一种由 Java 类或接口定义的 Java 数据类型。引用类型之所以得名,是因为这些值引用对象或包含对对象的引用。这个想法类似于其他语言(如 C)中的指针。
Java 使用引用类型java.lang.String
来表示字符数据序列或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);
}
|
但是,第一个代码的样式(带空白)更受欢迎,因为可读性更高。即使以较高的阅读速度,也可以更容易地区分方法体和方法头。