一、什么是异常
异常(Exception):程序的错误可能发生在编译期间,也可以发生在运行期间,发生在运行期间的错误称为异常。编译期间的错误可以由编译器发现并报错,运行期间的错误需要我们编写代码,借助异常处理机制来捕获。
异常代码示例
1. public class SimpleException { public static void main(String[] args) { int a = 5; int b = 0; int c = a / b; System.out.println(c); } }
运行结果
目前的主流编程语言(例如 Java、C++、c#、Ruby、Python 等)都提供了异常处理机制,用来捕获程序运行期间产生的错误,提高程序健壮性。
异常产生原因
在 Java 中一个异常的产生,主要有如下三种原因:
- Java 内部错误发生异常,Java 虚拟机产生的异常。
- 编写的程序代码中的错误所产生的异常,例如空指针异常、数组越界异常等。
- 通过 throw 语句手动生成的异常,一般用来告知该方法的调用者一些必要信息
如何看待异常
对于程序出现的异常,一般有两种解决方法,一是遇到错误就终止程序的运行。另一种方法就是程序员在编写程序时,就充分考虑到各种可能的异常和错误,极力预防和避免。实在无法避免的,要编写相应的代码进行异常的检测、以及异常的处理,保证代码的健壮性。
二、异常结构
常见的异常
|---java.lang.Throwable:异常体系的根父类
|---java.lang.Error:错误。Java虚拟机无法解决的严重问题。如JVM系统内部错误、资源耗尽等 严重情况,一般不编写针对性的代码进行处理。
|---StackOverflowError(深递归导致栈被耗尽而抛出的异常)
|---OutOfMemoryError(内存溢出异常)
|---java.lang.Exception:异常。可编写针对性代码进行处理
|---编译时异常,(受检异常)在执行javac.exe命令时,出现的异常
|---ClassNotFoundException 没有找到类
|---FileNotFoundException 没有找到文件
|---IOException IO异常
|---运行时异常,(非受检异常)在执行java.exe命令时,出现的异常
|---ArraylndexOutOfBoundException 数组索引越界
|---NullPointerException 空指针异常
|---ClassCastException 类型转换异常
|---NumberFormatException 数字转换异常
|---ArithmeticException 算数错误异常
|---IllegalArgumentException 使用非法实参调用方法
错误和异常的区别
Error(错误)和 Exception(异常)都是 java.lang.Throwable 类的子类,在 Java 代码中只有继承了 Throwable 类的实例才能被 throw 或者 catch。
Exception 和 Error 体现了 Java 平台设计者对不同异常情况的分类,Exception 是程序正常运行过程中可以预料到的意外情况,并且应该被开发者捕获,进行相应的处理。Error 是指正常情况下不大可能出现的情况,绝大部分的 Error 都会导致程序处于非正常、不可恢复状态。所以不需要被开发者捕获。
Error 错误是任何处理技术都无法恢复的情况,肯定会导致程序非正常终止。并且 Error 错误属于未检查类型,大多数发生在运行时。Exception 又分为可检查(checked)异常和不检查(unchecked)异常,可检查异常在源码里必须显示的进行捕获处理,这里是编译期检查的一部分。不检查异常就是所谓的运行时异常,通常是可以编码避免的逻辑错误,具体根据需要来判断是否需要捕获,并不会在编译器强制要求。
三、异常处理的方式
五个关键字
Java 异常处理机制通过五个关键字来实现,分别是 try、catch、throw、throws 和 finally:
- try 用来捕获异常;
- catch 用来匹配异常类型;
- finally 用来“善后”,它就像 default 语句,在任何情况下都会执行;
- throw 用来抛出异常;
- throws 用来声明可能会出现的异常
try-catch用法
注意:
try 和 catch 后面的花括号{ }
不可以省略,即使 try 块里只有一行代码,也不可省略这个花括号。
try 块中声明的变量只是代码块内的局部变量,它只在 try 块内有效,其它地方不能访问该变量。
try-catch-finally用法
try catch finally 语句块的执行情况可以细分为以下 3 种情况:
- 如果 try 代码块中没有拋出异常,则执行完 try 代码块之后直接执行 finally 代码块,然后执行 try catch finally 语句块之后的语句。
- 如果 try 代码块中拋出异常,并被 catch 子句捕捉,那么在拋出异常的地方终止 try 代码块的执行,转而执行相匹配的 catch 代码块,之后执行 finally 代码块。如果 finally 代码块中没有拋出异常,则继续执行 try catch finally 语句块之后的语句;如果 finally 代码块中拋出异常,则把该异常传递给该方法的调用者。
- 如果 try 代码块中拋出的异常没有被任何 catch 子句捕捉到,那么将直接执行 finally 代码块中的语句,并把该异常传递给该方法的调用者。
除非在 try 块、catch 块中调用了退出虚拟机的方法System.exit(int status)
,否则不管在 try 块或者 catch 块中执行怎样的代码,出现怎样的情况,异常处理的 finally 块总会执行。
throws声明异常
使用 throws 声明抛出异常时有一个限制,是方法重写中的一条规则:子类方法声明抛出的异常类型应该是父类方法声明抛出的异常类型的子类或相同,子类方法声明抛出的异常不允许比父类方法声明抛出的异常多。
使用 throws 声明抛出异常的思路是:
- 当前方法不知道如何处理这种类型的异常,该异常应该由向上一层的调用者处理;
- 如果顶层的 main() 方法也不知道如何处理这种类型的异常,也可以使用 throws 声明抛出异常,该异常将交给 JVM 处理。
throw抛出异常
与throws的区别:
1) throws 用来声明一个方法可能抛出的所有异常信息,表示出现异常的一种可能性,但并不一定会发生这些异常;throw 则是指拋出的一个具体的异常类型,执行 throw 则一定抛出了某种异常对象。
2) 通常在一个方法(类)的声明处通过 throws 声明方法(类)可能拋出的异常信息,而在方法(类)内部通过 throw 声明一个具体的异常信息。
3) throws 通常不用显式地捕获异常,可由系统自动将所有捕获的异常信息抛给上级方法; throw 则需要用户自己捕获相关的异常,而后再对其进行相关包装,最后将包装后的异常信息抛出。