一,基本概念
异常是程序在运行时出现的不正常情况。是Java按照面向对象的思想将问题进行对象封装。这样就方便于操作问题以及处理问题。
异常处理的目的是提高程序的健壮性。你可以在catch和finally代码块中给程序一个修正机会,使得程序不因不可控制的异常而影响程序的流程。同时,通过获取Java异常信息,也为程序的开发维护提供了方便。
Java异常类层次结构图
Java中的异常用对象来处理,并定义java.lang.Throwable作为所有异常的超类。Throwable分成了两个不同的分支,Exception(异常)和 Error(错误);
其中异常类Exception又分为运行时异常(RuntimeException)和非运行时异常。或不受检查异常(Unchecked Exception)和检查异常(Checked Exception);
异常是针对 方法 来说的,抛出、声明抛出、捕获和处理异常都是在方法中进行的;
Java异常处理通过5个关键字try、catch、throw、throws、finally进行管理;
Error(错误):灾难性的致命的错误,是程序无法控制和处理的。
Error类对象由 Java 虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关。例如,Java虚拟机运行错误、内存溢出。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止;还有发生在虚拟机试图执行应用时,如类定义错误、链接错误。这些错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。
Exception(异常):通常情况下是可以被程序处理的,并且在程序中应该尽可能的去处理这些异常。。
运行时异常和受检查异常
运行时异常 (unChecked异常):
RuntimeException类及其子类都被称为运行时异常。这些异常一般是由程序逻辑错误引起的,属于应该解决的Bug,程序应该从逻辑角度避免这类异常的发生,不推荐try-catch来捕获处理,但是有时候为了增强用户体验,保证Crash次数降到最低,会人为捕捉一些运行时异常。这种异常的特点是Java编译器不去检查它,也就是说,当程序中可能出现这类异常时,即使没有用try-catch语句捕获它,也没有用throws字句声明抛出它,还是会编译通过。但在运行时会被系统自动抛出。
非运行时异常 (checked异常):
除了RuntimeException类及其子类外,其他的Exception类及其子类都属于非运行时异常,从程序语法角度讲是必须进行处理的异常,如果不处理程序就不能编译通过。
异常转型和异常链:
我们做的JEE项目时候,一般会有三层的结构:持久层、逻辑层、展现层。异常也是如此的,当我们各个层之间传递异常,我们就需要先封装,然后传递。
异常链示例
catch (SQLException e) { throw new JdbcException(e); }
二、异常处理机制
在 Java 应用程序中,异常处理机制为:抛出异常,捕捉异常。
抛出异常
当一个方法出现错误引发异常时,方法创建异常对象并交付运行时系统,异常对象中包含了异常类型和异常出现时的程序状态等异常信息。运行时系统负责寻找处置异常的代码并执行。
该方法的调用者必须处理或者重新抛出该异常。当方法的调用者无力处理该异常的时候,应该继续抛出,所经方法都层层上抛获取的异常,若最终都没有被处理,将交由虚拟机处理。处理也很简单,就是打印异常消息和堆栈信息,记录日志。
捕捉异常
在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器(exception handler)。潜在的异常处理器是异常发生时依次存留在调用栈中的方法的集合。当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,即为合适的异常处理器。
运行时系统从发生异常的方法开始,依次回查调用栈中的方法,直至找到含有合适异常处理器的方法并执行。当运行时系统遍历调用栈而未找到合适的异常处理器,如果出现异常的线程为主线程,则整个程序运行终止;如果非主线程,则终止该线程,其他线程继续运行。
在方法中用try-catch语句捕获并处理异常,catach语句可以有多个,用来匹配处理异常。并且尽量将捕获底层异常类的catch子句放在前面。
异常总是先被抛出,后被捕捉的。
Java规定
对于可查异常必须捕捉、或者声明抛出。允许忽略不可查的RuntimeException和Error。
RuntimeException由业务逻辑保证。
3.1、抛出异常实例(throws 和 throw)
public class Throws { public static void main(String[] args) throws Exception {//抛出异常类 System.out.println(10 / 0); throw new Exception("抛出异常对象"); //System.out.println("throw后面的代码不再执行"); } }
3.2、捕获异常实例(try-catch 和 finally)
import java.util.InputMismatchException; import java.util.Scanner; public class TryCatch { public static void main(String[] args) { Scanner input = new Scanner(System.in); try { //可能会发生异常的程序代码 System.out.print("请输入一个数字:"); int a = input.nextInt(); } catch (InputMismatchException e) {// 捕捉异常 System.err.println("数据类型不符!"); e.printStackTrace(); System.err.println(e.getMessage()); // return; 若捕捉到异常会先执行finally, 再return。 } catch (Exception e) {// catch 先写子类,再写父类 System.out.println("再捕捉一次!"); // System.exit(0); } finally {// 除非执行System.exit(0),否则都会执行 System.out.println("finally 被执行!"); // 应用举例:确保关闭数据库,关闭流 } System.out.println("我还是被执行了!"); // 如果提前return,则不执行了 } }
throw 和throws关键字的区别
throw用于抛出异常对象,后面跟的是异常对象;throw用在方法内。
throws用于抛出异常类,后面跟的异常类名,可以跟多个,用逗号隔开。throws用在方法方法签名上。
通常情况:方法内容如果有throw,抛出异常对象,并没有进行处理,那么方法上一定要声明,否则编译失败。
三、Java常见异常
4.1、Error
LinkageError:链接错误;
ThreadDeath:线程死锁;
OutOfMemoryError:内存溢出;
StackOverflowError :堆栈溢出;
NoClassDefFoundError:类定义错误;
Virtual MachineError:虚拟机运行错误。
4.2、运行时异常(unChecked异常)
SecurityException:安全性异常;
NullPointerException:空指针异常;
ClassCastException:类型强制转换异常;
ClassNotFoundException:找不到类异常;
IllegalArgumentException:非法参数异常;
NegativeArraySizeException:数组长度为负异常;
ArithmeticException:算术条件异常。如:整数除零;
ArrayIndexOutOfBoundsException:数组下标越界异常;
ArrayStoreException:数组中包含不兼容的值抛出的异常;
StringIndexOutOfBoundsException:字符串下标越界异常;
ArrayStoreException:向数组中存放与声明类型不兼容对象异常;
4.3、非运行时异常(checked异常)
IOException:输入输出流异常;
SQLException:数据库操作异常;
EOFException:文件已结束异常;
TimeoutException:执行超时异常;
DataFormatException:数据格式化异常;
NoSuchFieldException:没有匹配的属性异常;
ClassNotFoundException:没有匹配的类异常;
FileNotFoundException:没有匹配的文件异常;
NoSuchMethodException:没有匹配的方法异常;
4.4、Throwable类的主要方法
public String getMessage():返回关于发生的异常的详细信息。这个消息在Throwable 类的构造函数中初始化了。
public Throwable getCause():返回一个Throwable 对象代表异常原因。
public String toString():使用getMessage()的结果返回类的串级名字。
public void printStackTrace():打印toString()结果和栈层次到System.err,即错误输出流。
public StackTraceElement [] getStackTrace():返回一个包含堆栈层次的数组。下标为0的元素代表栈顶,最后一个元素代表方法调用堆栈的栈底。
public Throwable fillInStackTrace():用当前的调用栈层次填充Throwable 对象栈层次,添加到栈层次任何先前信息中。
四、自定义异常实例
实例一:
class UserException extends Exception { // 继承父类 public UserException() { super(); } public UserException(String message) { super(message); } } public void activatioin(String code) throws UserException { try { User user = userDao.findByCode(code); if(user == null) throw new UserException("无效的激活码!"); if(user.isStatus()) throw new UserException("您已经激活过了"); userDao.updateStatus(user.getUid(), true); // 修改状态 } catch(SQLException e) { throw new RuntimeException(e); } }
实例二:
package Test; import java.lang.Exception; public class TestException { static int quotient(int x, int y) throws MyException { // 定义方法抛出异常 if (y < 0) { // 判断参数是否小于0 throw new MyException("除数不能是负数"); // 异常信息 } return x/y; // 返回值 } public static void main(String args[]) { // 主方法 int a =3; int b =0; try { // try语句包含可能发生异常的语句 int result = quotient(a, b); // 调用方法quotient() } catch (MyException e) { // 处理自定义异常 System.out.println(e.getMessage()); // 输出异常信息 } catch (ArithmeticException e) { // 处理ArithmeticException异常 System.out.println("除数不能为0"); // 输出提示信息 } catch (Exception e) { // 处理其他异常 System.out.println("程序发生了其他的异常"); // 输出提示信息 } } } class MyException extends Exception { // 创建自定义异常类 String message; // 定义String类型变量 public MyException(String ErrorMessagr) { // 父类方法 message = ErrorMessagr; } public String getMessage() { // 覆盖getMessage()方法 return message; } }
五、Java异常处理的原则和技巧
不要把自己能处理的异常抛给别人;
catch块尽量保持一个块捕获一类异常;
细化异常的类型,不要不管什么类型的异常都写成Excetpion;
避免过大的try块,不要把不会出现异常的代码放到try块里面;
如果把父类的异常放到前面,后面的catch语句块将得不到执行的机会;
尽量将异常统一抛给上层调用者,由上层调用者统一决定如何进行处理。
不要用try-catch参与控制程序流程,异常控制的根本目的是处理程序的非正常情况;
只要不是retry或者queue的情况,基本上所有的异常都是需要继续向上抛的,最终交给顶层异常处理机制(应用或者容器)。
Java异常处理三原则
具体明确
提早抛出
通过提早抛出异常(又称"迅速失败"),异常得以清晰又准确。堆栈信息立即反映出什么出了错(提供了非法参数值),为什么出错(文件名不能为空值),以及哪里出的错(readPreferences()的前部分)。
延迟捕获
异常发生时,不应立即捕获,而是应该考虑当前作用域是否有有能力处理这一异常的能力,如果没有,则应将该异常继续向上抛出,交由更上层的作用域来处理。
如何记录异常(写入日志)
在异常最开始发生的地方进行日志信息记录;
如果捕获到一个异常,但是这个异常是可以处理的。则无须记录异常;
捕获到一个未记录过的异常或外部系统异常时,应该记录异常的详细信息。
记录checked异常还是unChecked异常
如果一个异常是可以恢复的,可以被调用者正确处理的,使用checked异常;
如果一个异常是致命的,不可恢复的。或者调用者去捕获它没有任何益处,使用unChecked异常;
在使用unChecked异常时,必须在在方法声明中详细的说明该方法可能会抛出的unChekced异常。由调用者自己去决定是否捕获unChecked异常;
受检异常尽可能转化为非受检异常。
在类继承的时候,方法覆盖时如何进行异常处理
如果父类的方法声明一个异常,则子类在重时声明的异常范围应该不小于 父类;
如果父类或者接口中的方法没有抛出过异常,那么子类是不可以抛出异常的,如果子类的覆盖的方法中出现了异常,只能try不能throws;
如果这个异常子类无法处理,已经影响了子类方法的具体运算,这时可以在子类方法中,通过throw抛出RuntimeException异常或者其子类,这样,子类的方法上是不需要throws声明的。