处理错误
异常分类
所有的异常都是由Throwable继承而来,但是在下一层会被分解为两个分支:Error和Exception。
Error
Error类层次结构描述了Java运行时系统的内部错误和资源耗尽错误。
如果出现了Error,除了通知用户,并尽力妥善地终止程序以外,你几乎无能为力。
Exception
RuntimeException:由编程错误导致的异常
- 错误的强制类型转换
- 数组访问越界
- 访问null指针
其他异常:程序本身没有问题,但由于像I/O错误这类问题导致的异常
- 试图超越文件末尾继续读取数据
- 试图打开一个不存在的文件
- 试图根据给定的字符串查找Class对象,而这个字符串表示的类并不存在
Java语言规范将派生于Error类或RuntimeException类的所有异常称为非检查型(unchecked)异常,所有其他的异常称为检查型(checked)异常。编译器将检查你是否为所有的检查型异常提供了异常处理器。
声明检查型异常
一个方法必须声明所有可能抛出的检查型异常,非检查型异常要么再你的控制之外(Error),要么是由一开始就应该避免的情况导致的(RuntimeException)。如果你的方法没有声明所有可能发生的检查型异常,编译器就会发出一个错误信息。
如何抛出异常
- 找到一个合适的异常类
- 创建这个类的一个对象
- 将对象抛出
例如:
String gripe="Content-length: "+len+",Received: "+n;
throw new EOFException(gripe);
一旦方法抛出了异常,这个方法就不会返回到调用者。
创建异常类
我们需要做的只是定义一个派生于Exception的类,或者派生于Exception的某个子类。
习惯做法是:自定义的这个类应该包含两个构造器,一个是默认的构造器,另一个是包含详细描述信息的构造器。
捕获异常
try/catch语句块
try
{
code
}
catch(ExceptionType e)
{
handler for this type
}
异常处理的一般经验:
捕获那些你知道如何处理的异常,而继续传播那些你不知道怎样处理的异常。
如果想传播一个异常,就必须再方法的首部添加一个throws说明符,提醒调用者这个方法可能会抛出异常。
如果编写一个方法覆盖超类的方法,而这个超类方法没有抛出异常,你就必须捕获你的方法代码中出现的每一个检查型异常。不允许在子类的throws说明符中出现超类方法未列出的异常类。
再次抛出异常与异常链
可以在catch子句中抛出一个异常。通常,希望改变异常的类型时会这样做。
这样可以在子系统中抛出高层异常,而不会丢失原始异常的细节。
finally子句
为了解决在代码抛出异常时,方法的本地资源释放问题,引入了finally子句。
不管是否有异常被捕获,finally子句中的代码都会执行。
finally子句的体要用于清理资源。不要把改变控制流的语句(return,throw,break,continue)放在finally子句中。
try-with-Resources语句(带资源的try语句)
try(Resource res=...)
{
work with res
}
try块退出时,会自动调用res.close()。还可以同时指定多个资源。不论块如何退出,指定的多个资源都会关闭。
try-with-resources语句自身也可以有catch子句,甚至还可以有一个finally子句。这些子句会在关闭资源之后执行。
只要需要关闭资源,就要尽可能使用try-with-resources子句。
分析堆栈轨迹元素
两种方法
- 调用Throwable类的printStackTrace方法来访问堆栈轨迹的文本描述信息。
var t=new Throwable();
var out=new StringWriter();
t.printStackTrace(new PrintWriter(out));
String description=out.toString();
System.out.println(description);
这个方法效率不高,因为它要得到整个堆栈,即使调用者可能只需要几个栈帧。另外它只允许访问挂起方法的类名,而不能访问类对象。
- 使用StackWalker类,它会生成一个StackWalker.StackFrame实例流,其中每个实例分别描述一个栈帧(stack frame)。
var walker = StackWalker.getInstance();
walker.forEach(System.out::println);
使用异常的技巧
- 异常处理不能代替简单的测试(只在异常情况下使用异常)
- 不要过分地细化异常
不要将每一条语句都分装在一个独立的try语句块中。 - 充分利用异常层次结构
- 不要压制异常
- 在检测错误时,“苛刻”要比放任更好
- 不要羞于传递异常
使用断言
断言机制允许在测试期间向代码中插入一些检查,而在生产代码中会自动删除这些检查。
选择使用断言的时机
- 断言失败是致命的,不可恢复的错误
- 断言检查只是在开发和测试阶段打开
因此,不应该使用断言向程序的其他部分通知发生了可恢复性的错误,断言只应该用于在测试阶段确定程序内部错误的位置。
日志
稍微看了一下,具体细节等日后若有需要的话再来学习。