异常指程序运行过程中出现的非正常现象,例如用户输入错误、除数为零、需要处理的 文件不存在、数组下标越界等。
所谓异常处理,就是指程序在出现问题时依然可以正确的执行完。
Java 是采用面向对象的方式来处理异常的。处理过程:
- 抛出异常:在执行一个方法时,如果发生异常,则这个方法生成代表该异常的一个 对象,停止当前执行路径,并把异常对象提交给 JRE。
- 捕获异常:JRE 得到该异常后,寻找相应的代码来处理该异常。JRE 在方法的调用 栈中查找,从生成异常的方法开始回溯,直到找到相应的异常处理代码为止。
一、异常分类
Java 对异常进行了分类,不同类型的异常分别用不同的 Java 类表示,所有异常的根类 为 java.lang.Throwable,Throwable 下面又派生了两个子类:Error 和 Exception。
1. Error
Error 是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编 写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java 虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java 虚拟机(JVM)一般会选择线 程终止。 Error 表明系统 JVM 已经处于不可恢复的崩溃状态中。我们不需要管他。
2. Exception
Exception 是程序本身能够处理的异常,如:空指针异常(NullPointerException)、数 组 下 标 越 界 异 常 ( ArrayIndexOutOfBoundsException )、类 型 转 换 异 常 (ClassCastException)、算术异常(ArithmeticException)等。Exception 类是所有异常类的父类,其子类对应了各种各样可能出现的异常事件。
通常 Java 的异常可分为:
- RuntimeException 运行时异常
- CheckedException 已检查异常
3. RuntimeException
派生于 RuntimeException 的异常,如被 0 除、数组下标越界、空指针等,其产生比较频繁,处理麻烦,如果显式的声明或捕获将会对程序可读性和运行效率影响很大。 因此 由系统自动检测并将它们交给缺省的异常处理程序(用户可不必对其处理)。
这类异常通常是由编程错误导致的,所以在编写程序时,并不要求必须使用异常处理机 制来处理这类异常,经常需要通过增加“逻辑处理来避免这些异常”。
常见的运行时异常:
- ArithmeticException 异常:试图除以 0
public class Test3 {
public static void main(String[ ] args) {
int b=0;
System.out.println(1/b);
}
}
- NullPointerException 异常
public class Test4 {
public static void main(String[ ] args) {
String str=null;
System.out.println(str.charAt(0));
}
}
- ClassCastException 异常
class Animal{
}
class Dog extends Animal{
}
class Cat extends Animal{
}
public class Test5 {
public static void main(String[ ] args) {
Animal a=new Dog();
Cat c=(Cat)a;
}
}
- ArrayIndexOutOfBoundsException 异常
public class Test6 {
public static void main(String[ ] args) {
int[ ] arr = new int[5];
System.out.println(arr[5]);
}
}
- NumberFormatException 异常
public class Test7 {
public static void main(String[ ] args) {
String str = "1234abcf";
System.out.println(Integer.parseInt(str));
}
}
4. CheckedException
所有不是 RuntimeException 的异常,统称为 Checked Exception,又被称为“已检 查异常”,如 IOException、SQLException 等以及用户自定义的 Exception 异常。 这类 异常在编译时就必须做出处理,否则无法通过编译。
异常的处理方式有两种:使用“try/catch”捕获异常、使用“throws” 声明异常。
二、异常捕获
捕获异常是通过 3 个关键词来实现的:try-catch-finally。用 try 来执行一段程序,如 果出现异常,系统抛出一个异常,可以通过它的类型来捕捉(catch)并处理它,最后一步 是通过 finally 语句为异常处理提供一个统一的出口,finally 所指定的代码都要被执行 (catch 语句可有多条;finally 语句最多只能有一条,根据自己的需要可有可无)。
try{
可能出现异常的代码
}catch(异常类型 变量名){
出现异常时执行的代码
}....catch(异常类型 变量名){
出现异常时执行的代码
}finally{
最后要执行的代码
}
注意:
- 一个try后可以有1~n个catch
- catch是从上到下依次判断执行,try中没有出现异常,不会执行catch判断捕获。一旦出现异常,try中后面的代码不会执行
- 如果catch中捕获的异常类型比较大,应放在最后
- finally:当前try ... catch的结构执行结束后,会执行finally中的代码(一般为释放资源的代码)
例:
public class Test8 {
public static void main(String[ ] args) {
FileReader reader = null;
try {
reader = new FileReader("d:/a.txt");
char c = (char) reader.read();
char c2 = (char) reader.read();
System.out.println("" + c + c2);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (reader != null) {
reader.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
三、声明异常
当 CheckedException 产生时,不一定立刻处理它,可以再把异常 throws 出去。
在方法中使用 try-catch-finally 是由这个方法来处理异常。但是在一些情况下,当前方法并不需要处理发生的异常,而是向上传递给调用它的方法处理。
如果一个方法中可能产生某种异常,但是并不能确定如何处理这种异常,则应根据异常规范在方法的首部声明该方法可能抛出的异常。
如果一个方法抛出多个已检查异常,就必须在方法的首部列出所有的异常,之间以逗号隔开。
public static void readFile(String fileName) throws FileNotFoundException, IOException {
FileReader in = new FileReader(fileName);
int tem = 0;
try {
tem = in.read();
while (tem != -1) {
System.out.print((char) tem);
tem = in.read();
}
} finally {
if (in != null) {
in.close();
}
}
}
四、try-with-resource 自动关闭 Closable 接口的资源
public class Test8 {
public static void main(String[] args) {
try (FileReader reader = new FileReader("d:/a.txt");) { //try-with-resource
char c = (char) reader.read();
char c2 = (char) reader.read();
System.out.println("" + c + c2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
五、自定义异常
- 在程序中,可能会遇到 JDK 提供的任何标准异常类都无法充分描述清楚我们想要 表达的问题,这种情况下可以创建自己的异常类,即自定义异常类。
- 自定义异常类只需从 Exception 类或者它的子类派生一个子类即可。
- 自定义异常类如果继承 Exception 类,则为受检查异常,必须对其进行处理;如果不想处理,可以让自定义异常类继承运行时异常 RuntimeException 类。
- 习惯上,自定义异常类应该包含 2 个构造器:一个是默认的构造器,另一个是带 有详细信息的构造器。
class IllegalAgeException extends Exception {
public IllegalAgeException() {
}//带有详细信息的构造器,信息存储在 message 中
public IllegalAgeException(String message) {
super(message);
}
}
自定义异常的使用:
public void setAge(int age) throws IllegalAgeException {
if (age < 0) {
throw new IllegalAgeException("人的年龄不应该为负数");
}
this.age = age;
}