通过异常处理错误
- 一、异常的基本概念
- 1、使用异常的目的
- 2、异常的抛出过程
- 二、异常的使用
- 1、异常的捕获和处理语法
- 2、创建自定义异常及记录日志
- 3、异常说明
- 4、异常链
- 三、异常的限制
- 1、finally使用的限制
- 2、覆盖会抛出异常的方法限制
- 3、异常匹配的限制
- 四、异常的影响
- 1、异常对构造器的影响
- 2、异常都程序设计的影响
一、异常的基本概念
1、使用异常的目的
使用异常来处理错误的主要目的是设计出有着优异结构的代码。因为如果使用异常来处理错误的话就可以将代码主体和异常处理程序分开设计,使得程序设计者不必每次调用都进行错误处理,而只需将错误传递给异常处理程序(这可以简化错误处理代码的复杂度)。
使用异常的另一个好处是将错误报告机制规范化。因为异常处理是java中唯一正式的错误报告机制,这种情况会使调试变得简单。
2、异常的抛出过程
异常情形指的是阻止当前方法或者作用域执行的问题(一般来说是因为当前环境下无法获得解决问题的必要信息)。
抛出异常的过程是这样的:首先,将使用new在堆上创建异常对象;然后,当前的执行路径被终止,并从当前环境中弹出对异常对象的引用;最后,这个引用会在异常处理程序处被处理。
throw new NullPointerException("t = null")
//String参数是为了提供除类名外的异常的相关信息
二、异常的使用
1、异常的捕获和处理语法
Java使用try-catch-finally(catch子句和finally字句都可以没有)语句来捕获和处理异常:
try {
//可能产生异常的代码
} catch(Type1 id1) {
//对Type1异常的处理
} catch(type2 id2) {
//对Type1异常的处理
} finally {
//任何情况下都必须运行
}
2、创建自定义异常及记录日志
要定义自己的异常类,必须从已有的异常类继承(最好选择意思相近的异常类继承)。
class SimpleException extends Exception {}
对异常来说,最重要的是类名,它显示了异常的属性,蕴含异常处理程序处理异常所需要的信息。
可以通过java.util.logging工具将输出记录到日志中:
import java.util.logging.*;
import java.io.*;
class LoggingException extends Exception{
//静态logger用来记录产生的LoggingException异常
private static Logger logger =
Logger.getLogger("LoggingException");
public LoggingException(){
StringWriter trace = new StringWriter();
//printStackTrace不会默认产生字符串,所以需要PrintWriter参数
printStackTrace(new PrintWriter(trace));
//将信息保存下来
logger.severe(trace.toString());
}
}
当然,也可以记录其他人编写的异常。
3、异常说明
Java鼓励(也是除RuntimeException外的强制要求)人们把可能抛出的异常告知使用此方法的客户端程序员。
void f() throws TooBig, TooSmall, DivZero { //.....
这表示要么在方法里使用try-catch-finally处理掉异常并不抛出新的异常,要么就必须声明这个方法可能抛出的异常(编译期间会进行检查,称之为受检查异常)。你必须声明可能抛出的异常,但是你可以声明不抛出的异常(方便以后实现)。
4、异常链
常用的捕获异常状态的方法有:
//从Throwable继承的
String getMessage() //获取详细信息
String getLocalizedMessage() //获取用本地语言表示的详细信息
void printStackTrace() //输出异常抛出地点的栈轨迹
void printStackTrace(PrintStream)
void printStackTrace(java.io.PrintWriter)
Throwable fillInStackTrace() //返回一个拥有新的栈轨迹的异常
getStrackTrace()方法可返回由StackTraceElement组成的数组。
有时候希望异常处理的时候重新抛出一个新的异常(与使用fillInStackTrace一样),但会丢失之前捕获的异常和其轨迹栈,所以需要一种方式来记录之前的异常形成异常栈:
//三种基本类型Error、Exception、RuntimeException的话会提供带cause参数的构造器
throw new RuntimeException(e) //e是一个异常对象
e.initCause(new NullPointerException()) //如果没覆盖带cause参数的构造器时使用initCause()方法
三、异常的限制
1、finally使用的限制
使用finally可能会产生异常丢失问题:
//如果finally块中会返回异常,则会取代try里返回的异常
try{
//会返回一个异常
} finally {
//产生另一个异常,会替换前一个异常
}
//如果finally里面有return的话会直接丢失异常
try{
//会返回一个异常
} finally {
return; //会丢失异常并返回
}
2、覆盖会抛出异常的方法限制
如果想要在派生类中覆盖掉基类中会抛出异常的方法,需要注意以下几点:
- 被覆盖的方法可以不抛出异常;
- 被覆盖的方法抛出异常时必须是基类方法所抛出异常的子集或者子集的派生类,即不可以增加异常;
- 如果继承的基类和接口的同名方法所抛出的异常类不同,以类为准;
- 构造器不必遵守规则,但抛出的异常必须包含基类构造器的异常。
3、异常匹配的限制
抛出异常的时候,异常处理系统会按照代码书写的顺序,找到最近的处理程序。并且在找到匹配的处理程序后,不再继续查找。
四、异常的影响
1、异常对构造器的影响
如果构造器能够抛出异常的话,那么在使用构造器的时候就需要谨慎的处理非内存数据清理的问题。因为在构造对象的过程中会产生异常,而不能够使用对象的清理方法,甚至无法知道是否打开了非内存数据。
解决方法是可以用嵌套的try字句来确保安全:
public class Cleanup{
public static void main(String[] args)
{
try{
InputFile in = new InputFile("Cleanup.java");
try {
String s;
int i = 1;
while((s = in.getLine()) != null)
;
} catch (Exception e) {
System.out.println("Caught Exception in main");
e.printStackTrace(System.err);
} finally {
in.dispose();
}
} catch(Exception e) {
System.out.println("InputFile construction failed.");
}
}
}
2、异常都程序设计的影响
受检查异常有时候会强迫你处理你不知道该如何处理的异常,这时候不能够“吞食异常”,即直接捕获异常后不进行处理,这样会丢失异常信息。
有两种解决办法:
- 把异常传递给控制台(通过直接对main函数声明异常);
- 把“受检查异常”转换为“不受检查异常”。