异常:程序运行过程中,由于程序本身的错误或者外部环境的影响,而出现的异常情况
断言:为确保程序能正常运行或者排查程序出现异常的原因,需要编写一些测试代码进行测试。但程序正式运行时,是不允许测试代码工作的。如果直接删掉这些测试代码,再次遇到异常时,可能又需要重复编写相同的测试代码。可以通过断言来避免这些问题。
日志:当程序遇到异常时,并不能总是能同用户或者终端沟通。此时可以通过日志记录下问题,以备日后进行分析。
异常处理
当程序出现异常时,比如网络中断、文件格式错误、数组下标越界、null对象,如果程序正在进行某些操作而因为出现异常无法完成,程序应该能:
- 返回到安全状态,并让用户能执行其他操作
- 允许用户保存所有操作的结果,并以妥善的方式终止程序
异常处理的任务就是 将控制权 从异常产生的代码处 转移到 能够处理这种异常的处理器(ExceptionHandler)处。
如果方法不能够以正常的途径完成它的任务,那就通过另外一个路径退出方法。这种情况下,方法不返回任何值,而是抛出一个封装了错误信息的对象,并立即退出。这种情况下,调用这个方法的代码也无法继续执行,而是异常处理机制开始搜索能够处理这种异常状况的异常处理器(ExceptionHandler)。
几种类型的错误
用户输入错误、硬件错误(打印机关闭的、打印机打印过程中没有纸了)、物理限制(内存、硬盘满了,空间不足了)
代码错误
Throwable -> [ Error, Exception -> [ IOException, RuntimeException ] ]
Error是JVM自身错误,即JVM内部错误或者资源耗尽错误,应用程序不应该抛出这种类型的对象。
应用程序的错误(BUG)会抛出RuntimeException,常见的有:
- ClassCastException
- ArrayIndexOutOfBoundsException
- nullPointer
应用程序本身没有问题,而是IO等其它问题,会抛出IOException,常见的有:
- 文件指针已经到文件尾部:EOFException
- 试图打开一个不存在的文件:FileNotExistsException,尽管可以在打开之前先检查一下,但是有可能下一瞬间,它又被其它程序删除了。
- 根据给定的字符串找不到类:ClassNotFoundException
声明受检异常
在Exception中,RuntimeException是非受检异常(unchecked),其它是可受检异常(checked Exception)。
所有可能的受检异常都必须在方法中声明。
不需要声明JVM的内部错误,即Error,因为根本不可控。
也不需要声明RuntimeException,因为它是程序错误(BUG)引起的(而不是环境错误),是应该主动避免的。
声明、抛出、传递、捕获、处理
传递、抛出、声明
当代码可能出现受检异常时,最好的选择是什么也不做,而是将异常传递给调用者。传递的方式就是抛出异常,需要在
如果 read 方法出现了错误, 就 让 read 方法的调用者去操心!即声明read方法可能会拋出一个 IOException
捕获、处理
应该捕获那些知道如何处理的异常, 而将那些不知道怎样处理的异常继续进行传递。
仔细阅读一下 Java API 文档, 以便知道每个方法可能会抛出哪种异常, 然后再决定是自己处理,还是添加到 throws 列表中。
再次抛出异常与异常链
当超类的方法没有抛出异常,而子类重写后的方法代码可能会抛出受检异常时,需要在子类方法中自行捕获并处理。因为规则是子类抛出的异常不能大于父类。
这时就可以在catch语句中,将异常再次包装成一个非受检异常,并直接抛出它。
try{
}
catch(SQLException e){
Throwable throwable = new ServletException("database error");
throwable.initCause(e);
throw e
}
当上层调用方捕获到以上再次包装后的异常时,就可以用如下方式获取原始异常SQLException e:
throwable.getCause();
强烈建议使用这种包装技术,如果开发了一个供其他程序员使用的子系统,这样就可以用户抛出子系统中的高级异常,而不会丢失原始异常的细节。
强烈建议耦合try/catch 和 try/finally 语句块,这样可以提高代码的清晰度。方式如下:
try{
try{
// 工作代码
}
finally{
// 释放资源
}
}
catch(){
// 处理、或者抛出异常
}
这种方式不仅清楚,而且还有一个功能,就是会报告finally子句中出现的错误
带资源的try语句(try-with-resource)
如果在try块中打开了资源,并且这些资源实现了AutoCloseable接口,如InputStream\OutputStream\Reader\Writer等及其子类,那么就可以简写如下(try-with-resource):
public static void main(String[] args){
try(
Scanner in = new Scanner(new FileInputStream("IntegerProxy2.java"));
PrintWriter out = new PrintWriter("out.txt")
) // 在try关键字后加上一队小括号(),所有的要打开的实现了AutoCloseable的资源,都必须定义在这对小括号内,
// 这样,javac就知道finally要释放哪些资源了。
{
while(in.hasNextLine()){
out.println(in.nextLine());
}
} catch(FileNotFoundException e){
e.printStackTrace();
}
}
注意,只有实现了AutoCloseable的类型的变量才可以定义在try()的小括号内,如果在try()中定义一个String name="lily"
,编译无法通过。
异常处理注意
- 不要用异常处理代替测试,因为处理异常花费的时间远超正常的测试花费的时间
- 不要每条语句一个try-catch,而是把所有catch语句连在一起写,比较清晰
- 尽量将异常层次化,而不是所有的异常全都catch封装到一个Throwable或者Exception对象中去
- 将一种异常转换成另一种更加合适的异常时不要犹豫。例如在解析某个文件的一个整数时,捕获到了NumberFormatException异常,然后将它转换成IOException或者MySubsystemException的子类。
- **如果要调用的方法抛出的异常发生的可能性概率极低,可以用捕获后不做任何处理的方式直接关闭它。**但是不应该什么也不做,不应该既不捕获处理、也不抛出。
- 当当前方法的参数或者返回值为null时,应该根据实际业务选择是否抛出异常,在当前方法中抛出一个有具体明确的说明信息的Exception,比调用当前方法的方法抛出一个无明确信息的NullPointerException这样一个运行时异常RuntimeException更有用(有时可能需要根据业务的实际情况处理,有时业务允许结果为空)
- 底层的方法更应该传递异常,而不是捕获和处理异常。这样就让高层次的方法通知用户发生了错误、或者放弃不合适的命令