人非圣贤,孰能无过?
其实圣贤也会犯错,更何况是人(yuan)写的程序。
不过,程序出错时,只要能做到以下几点,用户一般就会原谅你:
- 用恰当的方式告知用户。
- 处理异常,把程序拉回安全的状态,让用户可以继续操作。
- 否则,保存数据并以合适的方式退出。
异常处理机制
Java 语言本身提供了一套异常处理机制:当我们的程序不能按照正常路径执行时,可以通过另一条路径退出。
在 Java 中,如果某个方法执行出错,就会抛出一个封装了错误信息的异常对象。
异常继承结构
学习 Java 的异常处理机制,既要学习它特定的语法,还要大致了解相关类的继承结构。
Java 的设计者把程序出错的类型分为:Error
和 Exception
,它们都继承自 Throwable
类。
只有当对象是 Throwable
类(以及它的子类)的实例时,才能通过 Java 虚拟机或者 throw
语句抛出。类似地, catch
语句也只能 catch
它的对象。
Throwable
对象一般包含其创建时的线程执行堆栈快照,错误信息的字符串,原因(cause)等内容。
Error
在 Java 中,Error
是非常严重的问题,例如,虚拟拟机崩溃、内存溢出之类的,程序不应尝试抛出这种类型的异常对象。
遇到这种错误,我们基本上也无能为力,让它随风飘逝吧。
Exception
Exception
又分为运行时异常( RuntimeException
)和非运行时异常。
运行时异常
RuntimeException
是 Java 虚拟机正常运行期间抛出的异常。
运行时异常一般是由开发者自己编写的程序导致的。
常见的有:
- 空指针异常:
NullPointerException
。 - 类型强制转换异常:
ClassCastException
。 - 参数异常:
IllegalArgumentException
。 - 数组越界异常:
ArrayIndexOutOfBoundsException
。
如果出现 RuntimeException
就该自己来擦屁股了。
非运行时异常
程序本身并没有错,但是由于执行了某些特定的操作又有可能会产生问题,编译器强制你先打预防针的,就属于非运行时异常。
常见的有:
- I/O 操作可能生成的异常:
IOException
。 - 数据库操作可能生成的异常:
SQLException
。 - 试图通过类名加载类可能生成的异常:
ClassNotFoundException
。
对于非运行时异常,如果我们能够处理,就处理。
如果不能处理,则应该往外抛出,让别人继续来处理,而不是粗暴地把它吃掉。
自定义异常
虽然 Java 的类库预定义了很多异常,但是如果这些都不是你的菜,那么自定义异常就是顺理成章的事。
自定义异常很简单,直接继承 Exception
或者是 RuntimeException
即可。
异常处理语法
关于 finally
和 return
的关系,只要记住 2 点就行了:
finally
总是会被执行。return
是当前方法的出口。
因此,只要有 finally
,无论是否发生异常,在你结束方法之前,都会跑到它那里。
如果 finally
里面有 return
,那么就意味着:不管你前面是否有 return
(或者是 throw
),执行到 finally
中 return
,就该说拜拜了。
总而言之,如果你想在 catch
中返回,或者是继续抛出异常,那么就不要在 finally
里面也返回。
另外,如果需要把已捕获的异常重新抛出,注意使用带 cause
的构造方法,或者是调用 initCause()
方法,来避免堆栈信息的丢失。
断言(assert)和异常的区别
- 断言是一种错误检查机制。
- 断言相当于前置条件,它的错误是致命的、不可恢复的。
- 断言一般只用于开发和测试阶段,在生产环境中应该禁用。