java核心技术卷I-异常

异常

在 Java 程序设计语言中, 异常对象都是派生于 Throwable 类的一个实例。稍后还可以看到,如果 Java 中内置的异常类不能够满足需求,用户可以创建自己的异常类。
在这里插入图片描述
在设计 Java 程序时, 需要关注 Exception 层次结构。 这个层次结构又分解为两个分支:一个分支派生于 RuntimeException ; 另一个分支包含其他异常。划分两个分支的规则是:由程序错误导致的异常属于 RuntimeException ; 而程序本身没有问题, 但由于像 I/O 错误这类问题导致的异常属于其他异常:
派生于 RuntimeException 的异常包含下面几种情况:

错误的类型转换。
数组访问越界 。
访问 null 指针。

不是派生于 RuntimeException 的异常包括:

试图在文件尾部后面读取数据。
试图打开一个不存在的文件。
试图根据给定的字符串查找 Class 对象, 而这个字符串表示的类并不存在。

“ 如果出现 RuntimeException 异常, 那么就一定是你的问题” 是一条相当有道理的规则。应该通过检测数组下标是否越界来避免 ArraylndexOutOfBoundsException 异常;应该通过在使用变量之前检测是否为 null 来杜绝 NullPointerException 异常的发生。
Java语言规范将派生于Error类或RuntimeException类的所有异常称为非受查(unchecked)异常,所有其他的异常称为受查(checked) 异常。

声明受查异常

如果遇到了无法处理的情况, 那么 Java 的方法可以抛出一个异常。这个道理很简单:一个方法不仅需要告诉编译器将要返回什么值,还要告诉编译器有可能发生什么错误。例如,一段读取文件的代码知道有可能读取的文件不存在, 或者内容为空,因此, 试图处理文件信息的代码就需要通知编译器可能会抛出 IOException 类的异常。
方法应该在其首部声明所有可能抛出的异常。这样可以从首部反映出这个方法可能抛出哪类受査异常。例如,下面是标准类库中提供的 FilelnputStream 类的一个构造器的声明

public FilelnputStream(String name) throws FileNotFoundException

这个声明表示这个构造器将根据给定的 String 参数产生一个 FilelnputStream 对象,但也有可能抛出一个 FileNotFoimdExc印tion 异常。如果发生了这种糟糕情况, 构造器将不会初始化一个新的 Hlel叩utStream 对象, 而是抛出一个 FileNotFoundException 类对象。 如果这个方法真的抛出了这样一个异常对象,运行时系统就会开始搜索异常处理器, 以便知道如何处理FileNotFoundException 对象„
在自己编写方法时, 不必将所有可能抛出的异常都进行声明。至于什么时候需要在方法
中用 throws 子句声明异常, 什么异常必须使用 throws 子句声明, 需要记住在遇到下面 4 种情况时应该抛出异常:

1 ) 调用一个抛出受査异常的方法, 例如, FilelnputStream 构造器。
2 ) 程序运行过程中发现错误, 并且利用 throw语句抛出一个受查异常。
3 ) 程序出现错误, 例如,a[-l]=0 会抛出一个 ArraylndexOutOffloundsException 这样的非受查异常。
4 ) Java 虚拟机和运行时库出现的内部错误。

如果出现前两种情况之一, 则必须告诉调用这个方法的程序员有可能抛出异常。因为任何一个抛出异常的方法都有可能是一个死亡陷阱。 如果没有处理器捕获这个异常,当前执行的线程就会结束。
对于那些可能被他人使用的 Java 方法, 应该根据异常规范(exception specification), 在方法的首部声明这个方法可能抛出的异常。

class HyAni(nation
{
    ...
	public Image loadlmage(String s) throws IOException
	{
	...
	} 
}

如果一个方法有可能抛出多个受查异常类型, 那么就必须在方法的首部列出所有的异常
类。每个异常类之间用逗号隔开

public Image loadlmage(String s) throws FileNotFoundException, EOFException

但是, 不需要声明 Java 的内部错误,即从 Error 继承的错误。任何程序代码都具有抛出那些异常的潜能,而我们对其没有任何控制能力。同样,也不应该声明从RuntimeException 继承的那些非受查异常。
这些运行时错误完全在我们的控制之下。如果特别关注数组下标引发的错误,就应该将更多的时间花费在修正程序中的错误上,而不是说明这些错误发生的可能性上。
总之,一个方法必须声明所有可能抛出的受查异常, 而非受查异常要么不可控制(Error),要么就应该避免发生(RuntimeException)。如果方法没有声明所有可能发生的受查异常, 编译器就会发出一个错误消息。
如果在子类中覆盖了超类的一个方法, 子类方法中声明的受查异常不能比超类方法中声明的异常更通用(也就是说, 子类方法中可以抛出更特定的异常, 或者根本不抛、出任何异常)。特别需要说明的是, 如果超类方法没有抛出任何受查异常, 子类也不能抛出任何受查异常。

如何抛出异常

对于一个已经存在的异常类, 将其抛出非常容易
1 ) 找到一个合适的异常类。
2 ) 创建这个类的一个对象。
3 ) 将对象抛出。

String readData(Scanner in) throws EOFException
...
throw new EOFException();

EOFException 类还有一个含有一个字符串型参数的构造器。 这个构造器可以更加细致的描述异常出现的情况。

创建异常类

在程序中,可能会遇到任何标准异常类都没有能够充分地描述清楚的问题。 在这种情况下,创建自己的异常类就是一件顺理成章的事情了。我们需要做的只是定义一个派生于Exception 的类,或者派生于 Exception 子类的类。

class FileFormatException extends IOException
{
    public FileFormatException() {}
    public FileFormatException(String gripe) {
	    super(gripe);
    }
}

这样定义好了之后就可以抛出了。例如:

String readData(BufferedReader in) throws FileFormatException

捕获异常

如果某个异常发生的时候没有在任何地方进行捕获,那程序就会终止执行,并在控制台上打印出异常信息, 其中包括异常的类型和堆栈的内容
要想捕获一个异常, 必须设置 try/catch语句块。

try{
more code
}
catch (ExceptionType e) {
handlerfor this type
}

如果在 try语句块中的任何代码抛出了一个在 catch 子句中说明的异常类, 那么
1 ) 程序将跳过 try语句块的其余代码。
2 ) 程序将执行 catch 子句中的处理器代码。
如果在 try 语句块中的代码没有拋出任何异常,那么程序将跳过 catch 子句。
如果方法中的任何代码拋出了一个在 catch 子句中没有声明的异常类型,那么这个方法就会立刻退出。
通常, 应该捕获那些知道如何处理的异常, 而将那些不知道怎样处理的异常继续进行传递。如果想传递一个异常, 就必须在方法的首部添加一个 throws 说明符, 以便告知调用者这个方法可能会抛出异常。
同时请记住,这个规则也有一个例外:如果编写一个覆盖超类的方法,而这个方法又没有抛出异常(如 JComponent 中的 paintComponent ), 那么这个方法就必须捕获方法代码中出现的每一个受查异常。不允许在子类的 throws 说明符中出现超过超类方法所列出的异常类范围。
异常对象可能包含与异常本身有关的信息。要想获得对象的更多信息, 可以试着使用

e.getMessage()

得到详细的错误信息(如果有的话,) 或者使用

e.getClass().getName()

得到异常对象的实际类型。

finally子句

当代码抛出一个异常时, 就会终止方法中剩余代码的处理,并退出这个方法的执行。如果方法获得了一些本地资源,并且只有这个方法自己知道,又如果这些资源在退出方法之前必须被回收,那么就会产生资源回收问题。
Java 有一种更好的解决方案,这就是 finally 子句。下面将介绍 Java 中如何恰当地关闭一个文件。如果使用 Java 编写数据库程序,就需要使用同样的技术关闭与数据库的连接。
这里, 强烈建议解搞合 try/catch 和 try/finally 语句块。这样可以提高代码的清晰度。

InputStrean in = . . .;
try
{
    try
    {
	    code that might throwexceptions
    }
    finally
    {
    	in.close();
    }
}
catch (IOException e) {
	show error message
}

内层的 try语句块只有一个职责, 就是确保关闭输入流。外层的 try 语句块也只有一个职责, 就是确保报告出现的错误。这种设计方式不仅清楚, 而且还具有一个功能,就是将会报告 finally 子句中出现的错误。
当 finally 子句包含 return 语句时, 将会出现一种意想不到的结果„ 假设利用 return语句从 try语句块中退出。在方法返回前, finally 子句的内容将被执行。如果 finally 子句中也有一个 return 语句,这个返回值将会覆盖原始的返回值。

public static int f(int n) {
    try
    {
	    int r = n * n;
	    return r; 
    }
    finally
    {
	    if (n = 2) return 0; 
    } 
}

如果调用 f(2), 那么 try 语句块的计算结果为 r = 4, 并执行 return 语句。然而,在方法真正返回前,还要执行 finally 子句。finally 子句将使得方法返回 0, 这个返回值覆盖了原始的返回值 4。
有时候, finally 子句也会带来麻烦。例如, 清理资源的方法也有可能抛出异常。现在,假设在 try 语句块中的代码抛出了一些非 IOException 的异常,这些异常只有这个方法的调用者才能够给予处理。执行 finally 语句块,并调用 dose 方法。而 close 方法本身也有可能抛出IOException 异常。当出现这种情况时, 原始的异常将会丢失,转而抛出close 方法的异常。这会有问题, 因为第一个异常很可能更有意思。如果你想做适当的处理,重新抛出原来的异常, 代码会变得极其繁琐。

带资源的 try 语句

带资源的 try 语句(try-with-resources) 的最简形式为:

try (Resource res = . . .) {
	work with res
}

try块退出时,会自动调用 res.close()。

使用异常机制的技巧

1、异常处理不能代替简单的测试
2、不要过分地细化异常
3、利用异常层次结构
4、不要压制异常
5、在检测错误时,“ 苛刻 ” 要比放任更好
6、不要羞于传递异常

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

局外人一枚

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值