2.10 错误类型

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

编译时错误(compile-time error)指的是因违反 Java 语法(syntax)规则而导致的错误。例如,括号和大括号必须成对出现,所以 (1 + 2) 是合法的,而 8) 是非法的。8) 导致程序无法编译,而编译器将显示一条错误消息。

编译器显示的错误消息通常会指出错误出现在程序的什么地方,有时还可以准确地指出错误。我们来重温一下第 1 章中的 Hello World 程序。

  1. public class Hello {
  2. public static void main(String[] args) {
  3. // 生成一些简单的输出
  4. System.out.println("Hello, World!");
  5. }
  6. }

如果遗漏了打印语句末尾的分号,将出现类似于以下的错误消息:

  1. File: Hello.java [line: 5]
  2. Error: ';' expected

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

然而,并非所有的错误消息都是容易理解的。有时编译器报告的错误位置不准确;有时对错误的描述模棱两可,几乎没什么帮助。

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

  1. File: Hello.java [line: 7]
  2. Error: reached end of file while parsing

这里有两个问题。首先,这条错误消息是从编译器的角度而不是你的角度生成的。分析(parsing)指的是在转换前读取程序的过程;如果编译器到达文件末尾后分析还在进行的话,那么就意味着程序遗漏了什么东西,但编译器不知道遗漏了什么,也不知道在何处遗漏的。它认为错误发生在程序末尾(第 7 行),但遗漏的大括号应该在前一行。

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

刚从事编程的几周内,你可能会为找出编译错误花费大量的时间,但随着经验越来越丰富,你犯的错误将越来越少,找出错误的速度也将越来越快。

第二种错误是运行时错误(run-time error),因其要等到程序运行后才会出现而得名。在 Java 中,这种错误发生在解释器执行字节码期间,也被称为异常,因为它们通常表明出现了异常而糟糕的情况。

本书前几章的简单程序中很少出现运行时错误,因此可能需要过段时间才能见到它们。运行时错误发生时,解释器将显示一条错误消息,指出在什么地方出现了什么问题。

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

  1. 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 程序存在一个逻辑错误:

  1. public class Hello {
  2. public static void main(String[] args) {
  3. System.out.println("Hello, ");
  4. System.out.println("World!");
  5. }
  6. }

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

  1. Hello,
  2. World!

如果我们要在一行中显示全部输出,那么上述输出就不正确。问题出在第 1 行,它用的是 println,而我们原本想用的是 print(参见 1.5 节中的 goodbye world 示例)。

有时很难找出逻辑错误,因为你必须进行反向推导:根据输出结果推断程序行为不正确的原因,并确定如何让它的行为正确无误。编译器和解释器在这方面帮不了你,因为它们并不知道正确的行为是什么样的。

了解这三种错误后,你应该阅读一下附录 C,其中搜集了一些我们最喜欢的调试建议。因为这些建议涉及了一些还未讨论的语言功能,所以你可能需要时不时地再次阅读这个附录。