Java 之道/简介
Java 之道
这本书和这门课的目标是教你像计算机科学家一样思考。我喜欢计算机科学家思考的方式,因为他们结合了数学、工程和自然科学的最佳特征。像数学家一样,计算机科学家使用形式语言来表示思想(特别是计算)。像工程师一样,他们设计事物,将组件组装成系统并评估备选方案之间的权衡。像科学家一样,他们观察复杂系统的行为,形成假设并检验预测。
计算机科学家最重要的技能是解决问题。我的意思是能够提出问题,创造性地思考解决方案,并清晰准确地表达解决方案。事实证明,学习编程的过程是练习解决问题能力的绝佳机会。这就是本章被称为“编程之道”的原因。
从一个层面上来说,你将学习编程,这本身就是一项有用的技能。从另一个层面上来说,你将使用编程作为达成目的的手段。随着我们的不断学习,这个目标将变得更加清晰。
你将学习的编程语言是Java,它相对较新(Sun 于 1995 年 5 月发布了第一个版本)。Java 是高级语言的一个例子;你可能听说过的其他高级语言包括 C、C++、Pascal 和 FORTRAN。
正如你可能从“高级语言”这个名称中推断的那样,也存在低级语言,有时被称为机器语言或汇编语言。通俗地说,计算机只能执行用低级语言编写的程序。因此,用高级语言编写的程序必须在运行之前进行翻译。这种翻译需要一些时间,这是高级语言的一个小缺点。
但它的优势是巨大的。首先,用高级语言编程要容易得多;“容易得多”指的是编写程序所需的时间更少,程序更短更易读,并且更有可能正确。其次,高级语言是可移植的,这意味着它们可以在不同的计算机上运行,几乎不需要或根本不需要修改。低级程序只能在一类计算机上运行,必须重新编写才能在其他计算机上运行。
由于这些优势,几乎所有程序都是用高级语言编写的。低级语言仅用于少数特殊应用程序。
翻译程序有两种方法:解释或编译。解释器是一个程序,它读取高级程序并执行其中的指令。实际上,它逐行翻译程序,交替读取行和执行命令。解释语言的例子包括 BASIC、Perl 和 Python。
编译器是一个程序,它读取高级程序并将其全部翻译成机器语言,然后执行任何命令。通常你会将程序作为单独的步骤进行编译,然后在稍后执行编译后的代码。在这种情况下,高级程序被称为源代码,翻译后的程序被称为可执行文件。
例如,假设你用 C 语言编写了一个程序。你可能会使用文本编辑器来编写程序(文本编辑器是一个简单的文字处理器)。程序完成后,你可能会将其保存在名为 program.c 的文件中,其中 program 是你起的任意名称,后缀 .c 是一种约定,表示该文件包含 C 源代码。
然后,根据你的编程环境的类型,你可能会退出文本编辑器并运行编译器。编译器将读取你的源代码,将其翻译并创建一个名为 program.o 的新文件来保存目标代码,或一个名为 program.exe 的文件来保存可执行文件。
Java 语言不同寻常,因为它既是编译的又是解释的。Java 编译器不会将 Java 程序翻译成机器语言,而是生成 Java 字节码。字节码易于(且快速)解释,就像机器语言一样,但它也具有可移植性,就像高级语言一样。因此,可以在一台机器上编译 Java 程序,通过网络将字节码传输到另一台机器,然后在另一台机器上解释字节码。这种能力是 Java 相对于许多其他高级语言的优势之一。
虽然这个过程可能看起来很复杂,但好消息是,在大多数编程环境(有时称为开发环境)中,这些步骤都是为你自动化的。通常你只需要编写程序并输入一个命令来编译和运行它。另一方面,了解后台发生的步骤是有用的,这样如果出现问题,你就可以找出问题所在。
程序的源代码包含一系列指令,这些指令指定如何执行计算。计算可能是数学运算,例如解方程组或求多项式的根,但也可能是符号运算,例如搜索和替换文档中的文本,或者(奇怪的是)编译程序。
指令(或命令,或语句)在不同的编程语言中看起来不同,但有一些基本功能几乎出现在每种语言中。
- 输入——从键盘、文件或其他设备获取数据。
- 输出——在屏幕上显示数据或将数据发送到文件或其他设备。
- 数学——执行基本数学运算,例如加法和乘法。
- 测试——检查某些条件并执行相应的语句序列。
- 重复——重复执行某些操作,通常带有某种变化。
信不信由你,这基本上就是全部。你用过的每一个程序,无论多么复杂,都是由看起来或多或少像这些函数的函数组成的。因此,描述编程的一种方法是将一个大型复杂任务分解成越来越小的子任务,直到最终这些子任务足够简单,可以使用这些简单函数来执行。
编程是一个复杂的过程,由于它是人完成的,因此经常会导致错误。由于一些奇特的原因,编程错误被称为 bug,而追踪和纠正它们的过程被称为调试。
程序中可能会出现几种不同类型的错误,区分它们有助于更快地追踪它们。
编译器只有在程序语法正确的情况下才能翻译程序;否则,编译将失败,你将无法运行程序。语法是指程序的结构以及关于该结构的规则。
例如,在英语中,句子必须以大写字母开头,以句号结尾。这句句子包含语法错误。这句也一样
对于大多数读者来说,几个语法错误不是什么大问题,这就是为什么我们能够阅读 E. E. Cummings 的诗歌而不会喷出错误消息。
编译器不会那么宽容。如果你的程序中存在任何语法错误,编译器会打印错误消息并退出,你将无法运行你的程序。
更糟糕的是,Java 中的语法规则比英语中的多,而你从编译器获得的错误消息通常没有太大帮助。在你编程生涯的前几周,你可能会花费大量时间追踪语法错误。然而,随着你积累经验,你会犯更少的错误,并能更快地发现它们。
运行时错误
[edit | edit source]第二种类型的错误是运行时错误,之所以这样称呼是因为错误直到你运行程序才会出现。在 Java 中,运行时错误发生在解释器运行字节码时出现问题。
目前的好消息是,Java 往往是一种安全的语言,这意味着运行时错误很少见,尤其是在接下来的几周中,我们将编写的简单程序类型。
在学期的后期,你可能会开始看到更多的运行时错误,特别是在我们开始讨论对象和引用时(对象章节)。
在 Java 中,运行时错误被称为异常,在大多数环境中,它们以包含有关发生情况和程序在发生时正在执行操作的信息的窗口或对话框的形式出现。此信息对于调试很有用。
逻辑错误和语义
[edit | edit source]第三种类型的错误是逻辑错误或语义错误。如果你的程序中存在逻辑错误,它将成功编译和运行,从某种意义上说,计算机不会生成任何错误消息,但它不会做正确的事情。它会做其他事情。具体来说,它会按照你指示它做的事情来做。
问题在于,你编写的程序不是你想要编写的程序。程序的含义(语义)是错误的。识别逻辑错误可能很棘手,因为它要求你通过查看程序的输出并试图弄清楚它正在做什么来反向操作。
实验性调试
[edit | edit source]你在这门课中学到的最重要的技能之一就是调试。虽然调试可能会让人沮丧,但调试是编程中最具智力挑战性、最具挑战性和最有趣的方面之一。
从某种意义上说,调试就像侦探工作。你面对着线索,你必须推断导致你看到的结果的过程和事件。
调试也像实验科学。一旦你有了关于哪里出错的想法,你就修改你的程序并再次尝试。如果你的假设是正确的,那么你可以预测修改的结果,并且你离编写一个有效的程序更近一步。如果你的假设是错误的,你必须想出一个新的假设。正如夏洛克·福尔摩斯指出的那样,“当你排除了不可能的事情,剩下的,无论多么不可能,都必须是真相。”(来自阿瑟·柯南·道尔的《四签名》)。
Holmes, Sherlock Doyle, Arthur Conan
对于有些人来说,编程和调试是一回事。也就是说,编程是逐步调试程序直到它完成你想要的操作的过程。这个想法是,你应该始终从一个执行某些操作的有效程序开始,并进行小的修改,并在进行时调试它们,这样你始终拥有一个有效的程序。
例如,Linux 是一个包含数千行代码的操作系统,但它最初是一个简单的程序,Linus Torvalds 用它来探索英特尔 80386 芯片。据拉里·格林菲尔德说,“Linus 的早期项目之一是一个在打印 AAAA 和 BBBB 之间切换的程序。这后来发展成为 Linux”(来自《Linux 用户指南测试版 1》)。
在后面的章节中,我将对调试和其他编程实践提出更多建议。
形式语言与自然语言
[edit | edit source]自然语言是人们所说的语言,比如英语、西班牙语和法语。它们不是由人们设计的(尽管人们试图对它们施加一些秩序);它们是自然演化的。
形式语言是由人们为特定应用而设计的语言。例如,数学家使用的符号是一种形式语言,它特别擅长表示数字和符号之间的关系。化学家使用形式语言来表示分子的化学结构。同样,编程语言是旨在表达计算的形式语言。
当你阅读英语中的句子或形式语言中的语句时,你必须弄清楚句子的结构是什么(尽管在自然语言中你是下意识地做到的)。这个过程被称为解析。
例如,当你听到“另一只鞋掉了”这句话时,你明白“另一只鞋”是主语,“掉了”是谓语。一旦你解析了一个句子,你就可以弄清楚它的意思,即句子的语义。假设你知道什么是鞋子,以及掉下来的意思,你将理解这句话的一般含义。
虽然形式语言和自然语言有很多共同点(标记、结构、语法和语义等),但也存在很多差异
- 歧义 自然语言充满了歧义,人们通过使用语境线索和其他信息来处理歧义。形式语言被设计成几乎或完全没有歧义,这意味着任何语句都只有一个意思,无论语境如何。
- 冗余 为了弥补歧义并减少误解,自然语言运用了大量的冗余。因此,它们往往很冗长。形式语言的冗余度较低,更简洁。
- 字面意义 自然语言充满了习语和比喻。如果我说“另一只鞋掉了”,可能根本没有鞋子,也没有什么掉下来。形式语言的意思就是它们说的意思。
从小就说自然语言的人(每个人)往往很难适应形式语言。在某些方面,形式语言和自然语言之间的差异就像诗歌和散文之间的差异,但更甚。
- 诗歌 词语的使用既是为了它们的音韵,也是为了它们的意思,而整首诗一起创造了一种效果或情感反应。歧义不仅很常见,而且往往是故意的。
- 散文 词语的字面意思更重要,结构的意义也更大。散文比诗歌更容易分析,但仍然经常存在歧义。
- 程序 计算机程序的含义是明确的和字面的,可以通过对标记和结构的分析完全理解。
阅读程序的提示
[edit | edit source]以下是一些阅读程序(和其他形式语言)的建议。首先,请记住,形式语言比自然语言密集得多,因此阅读它们需要更长的时间。此外,结构非常重要,因此通常不建议从上到下、从左到右阅读。相反,学会在脑海中解析程序,识别标记并解释结构。最后,请记住,细节很重要。像拼写错误和标点符号错误这样的小事情,你可以在自然语言中忍耐,但在形式语言中却会有很大影响。
第一个程序
[edit | edit source]你好,世界!
传统上,人们在新的语言中编写的第一个程序被称为你好,世界。因为它只做一件事情,就是打印你好,世界。在 Java 中,这个程序看起来像这样
class Hello
{
// main: generate some simple output
public static void main(String[] args) {
System.out.println("Hello, world.");
}
}
有时,编程语言的质量是根据你好,世界。程序的简单性来判断的。按照这个标准,Java 表现不佳。即使是最简单的 Java 程序也包含许多难以向初学者解释的功能。现在我们将忽略其中很多,但我将解释其中的一些。
所有程序都由类定义组成,类定义具有以下形式
类
[edit | edit source] class CLASSNAME
{
public static void main(String[] args) {
STATEMENTS
}
}
这里 CLASSNAME 表示你编造的任意名称。示例中的类名称是 Hello。
main
[edit | edit source]在第二行代码中,你可以先忽略 `public static void` 这几个词,但请注意 `main` 这个词。`main` 是一个特殊的名称,它指示程序开始执行的位置。当程序运行时,它会从 `main` 中的第一条语句开始执行,并依次执行,直到到达最后一条语句,然后退出。
在 `main` 中可以包含任意数量的语句,但这个例子只包含一个语句。它是一个打印语句,意味着它会在屏幕上打印一条消息。有点令人困惑的是,"打印"有时意味着"在屏幕上显示内容",有时又意味着"将内容发送到打印机"。在这本书中,我不会过多地讨论将内容发送到打印机,我们将所有打印操作都在屏幕上进行。
在屏幕上打印内容的命令是 `System.out.println`,括号中的内容就是将要打印的内容。语句末尾有一个分号 (;),这是每条语句末尾都必须包含的符号。
关于这个程序的语法,你应该注意以下几点。首先,Java 使用花括号 { 和 } 来将代码分组在一起(花括号也被称为大括号)。最外层的花括号(第 1 行和第 8 行)包含类定义,内部花括号包含 `main` 的定义。
另外,请注意第 3 行以 `//` 开头。这表示该行包含注释,注释是可以在程序中间添加的英文文本,通常用于解释程序的功能。当编译器遇到 `//` 时,它会忽略从该位置到行尾的所有内容。
在第一个实验中,你将编译并运行这个程序,还会对其进行各种修改,以便了解语法规则,并观察编译器在违反规则时会生成哪些错误消息。
- 问题求解:制定问题、寻找解决方案并表达解决方案的过程。
- 高级语言:像 Java 这样的编程语言,旨在便于人类阅读和编写。
- 低级语言:旨在便于计算机执行的编程语言。也称为“机器语言”或“汇编语言”。
- 形式语言:人们为特定目的设计的所有语言,比如用来表示数学思想或计算机程序。所有编程语言都是形式语言。
- 自然语言:人们自然进化而来的语言。
- 可移植性:程序可以在多种类型的计算机上运行的属性。
- 解释:通过逐行翻译高级语言程序来执行程序。
- 编译:将高级语言程序翻译成低级语言,一次性完成,为以后执行做准备。
- 源代码:高级语言程序,在编译之前。
- 目标代码:编译器输出,在翻译程序之后。
- 可执行文件:准备执行的目标代码的另一种名称。
- 字节码:用于 Java 程序的一种特殊目标代码。字节码类似于低级语言,但它像高级语言一样具有可移植性。
- 算法:解决一类问题的通用流程。
- 错误:程序中的错误。
- 语法:程序的结构。
- 语义:程序的含义。
- 解析:检查程序并分析其语法结构。
- 语法错误:程序中的错误,使其无法解析(因此也无法编译)。
- 异常:程序中的错误,使其在运行时发生故障。也称为运行时错误。
- 逻辑错误:程序中的错误,使其执行的结果与程序员的意图不符。
- 调试:查找并消除任何三种类型错误的过程。