ITEEDU

Java Gossip: 例外的继承架构

Java的例外处理机制并不是只有将程序逻辑与例外处理分开的好处,程序设计的错误情况很多且难以估计,并没有人能保证自已所设计的程序完全无误,例外处理最重要的是为程序设计人员提供种种可能的例外情况,让程序设计人员能够掌握并设法排除。

Java编译器会检查程序语法等的相关错误,这些错误是属于「编译时期错误」,然而语法无误并不代表程序逻辑没有错误,逻辑上的错误会在程序执行时发生,这是属于「执行时期错误」,而即使逻辑没有错误,也可能因为I/O、网络或甚至内存不足等情况而发生错误。

Java所处理的例外主要可分为两大类,一种是严重的错误,例如硬件错误或内存不足等问题,与此相关的类别是位于java.lang下的Error类别;另一种是非严重的错误,代表可以处理的状况,与此相关的是位于java.lang下的Exception类别。

Error类别与Exception类别都继承自Throwable类别,Throwable类别拥有几个报告相关例外讯息的方法:

getLocalizedMessage()

目前对象的描述

getMessage()

取得对象的错误讯息

printStackTrace()

取得堆栈中的讯息

除了使用这些方法之外,我们也可以利用toString()取得例外对象的错误描述。

您所处理的例外通常都是衍生自Exception类别,其中大部份是执行时期例外(RuntimeException), 例如 ArithmeticException、ArrayIndexOutOfBoundsException等等,另外还有一些非执行时期例外,例如 ClassNotFoundException(尝试加载类别时失败所引发,例如类别档案不存在)、InterruptedException(执行绪非 执行中而尝试中断所引发的例外)等等, 以下列出一些重要的继承架构:

Throwable
  Error(严重的系统错误)
    LinkageError
    ThreadDeath
    VirtualMachineError
    ....
  Exception
    ClassNotFoundException
    CloneNotSupportedException
    IllegalAccessException
    ....
    RuntimeException(执行时期例外)
      ArithmeticException
      ArrayStoreException
      ClassCastException
      ....

属于RuntimeException衍生出来的类别,是在执行时期会发生的,不需要特别使用try-catch或是在函式上使用"throws"宣告也 可以通过编译,例如您在使用数组时,并不一定要处理ArrayIndexOutOfBoundsException例外。

Exception下非RuntimeException衍生之例外如果有引发的可能性,则您一定要在程序中明确的指定处理才可以通过编译,例如当您使用 到BufferedReader类别时,由于有可能引发IOException,您要不就在try-catch中处理,要不就在函式上使用throws表 示由呼叫它的函式来处理。

了解例外处理的继承架构是必须的,例如在捕捉例外对象时,如果父类别例外对象撰写在子类别例外对象之前被捕捉,则catch子类别例外对象的区块将永远不会被执行,事实上编译器也会帮您检查这个错误,例如:

UseException.java
import java.io.*;
public class UseException {
	public static void main(String[] args) {
		try {
			throw new ArithmeticException("例外测试");
		}
		catch(Exception e) {
			System.out.println(e.toString());
		}
		catch(ArithmeticException e) {
			System.out.println(e.toString());
		}
	}
}

这个程序若在编译时将会产生以下的错误讯息:

UseException.java:11: exception java.lang.ArithmeticException has already been caught
catch(ArithmeticException e) {
^
1 error

要完成这个程序的编译,您必须更改例外对象捕捉的顺序,例如:

UseException.java
import java.io.*;
public class UseException {
	public static void main(String[] args) {
		try {
			throw new ArithmeticException("例外测试");
		}
		catch(ArithmeticException e) {
			System.out.println(e.toString());
		}
		catch(Exception e) {
			System.out.println(e.toString());
		}
	}
}

执行结果:

java.lang.ArithmeticException:  例外测试  

在撰写程序时,您也可以如上将Exception例外对象的捕捉撰写在最后,以便捕捉到所有尚未考虑到的例外,并进一步改进程序。

如果您要自订自已的例外类别,您可以继承Exception类别而不是Error,Error是属于严重的系统错误,您不用去处理它,您也可以继承 RuntimeException类别,就如之前所说过的,这个例外不一用明确使用try-catch来处理也可以通过编译,但通常建议的是继承 Exception,至少这样的程序会是比较安全的,对于可处理的这些例外,您在程序中必须明确的解决它,如果没有,编译器会告诉您。

一些程序会自行继承相关的例外类别,包括一些相关的例外讯息,它们也会在定义接口(interface)时于方法上声明throws某些类型的例外,然而 如果您在这些方法中发生了某些不是方法声明的例外(可能由于使用的底层技术不同而有这种情况),您就无法将之throw,只能自行撰写一些try.. catch来暗自处理掉,如果想要让这些例外丢出至上层,就要更多道的手续了,例如撰写一个类继承RuntimeException,在发生例外时将该例 外包装至这个RuntimeException类中,然后再丢出。