程序运行时,发生的不被指望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常。异常发生时,是任程序自生自灭,马上退出终止,仍是输出错误给给用户?或者用 C 语言风格:用函数返回值做为执行状态?
Java 提供了更加优秀解决办法:异常处理机制。java
异常处理机制能让程序在异常发生时,按照代码预先设定的异常处理逻辑,针对性地处理异常,让程序尽最大可能恢复正常并继续执行,且保持代码的清晰。
Java 中的异常能够是函数中语句执行时引起的,也能够是程序员经过 throw 语句手动抛出的,只要在 Java 程序中产生了异常,就会用一个对应类型的异常对象来封装异常,JRE 就会试图寻找异常处理程序来处理异常。程序员
Throwable 类是 Java 异常类型的顶层父类,一个对象只有是 Throwable 类的(直接或间接)实例,它才是一个异常对象,才能被异常处理机制识别。JDK 中内建了一些经常使用的异常类,咱们也能够自定义异常。数据库
Java 标准库内建了一些通用的异常,这些类以 Throwable 为顶层父类。
Throwable 又派生出 Error 类和 Exception 类。编程
错误:Error 类以及它的子类的实例,表明 JVM 自己的错误。错误不能被程序员经过代码处理,Error 不多出现。所以,程序员应该关注 Exception 为父类的分支下的各类异常类。数组
异常:Exception 以及它的子类,表明程序运行时发送的各类不被指望发生的事件。能够被 Java 异常处理机制使用,是异常处理的核心。 多线程
整体上咱们根据 Java 对异常的处理要求,将异常分为两类。编程语言
非检查异常(unchecked exception)
Error 和 RuntimeException 以及它们的子类。Java 在编译时,不会提示和发现这样的异常,不要求在程序中处理这些异常。因此若是愿意,咱们能够编写代码处理(使用 try...catch...finally)这样的异常,也能够不处理。对于这些异常,咱们更应该的不是去处理这些异常,而是应该修正代码。这样的异常发生的缘由多半是代码逻辑写的有问题。如除 0 错误 ArithmeticException,错误的强制类型转换错误 ClassCastException,数组索引越界错误 ArrayIndexOutOfBoundsException,操做了空对象错误 NullPointerException 等等。ide
检查异常(checked exception)
除了 Error 和 RuntimeException 的其它异常。Java 强制要求程序员为这样的异常作预备处理工做(使用 try...catch...finally 或者 throws)。在方法中要么用 try...catch 语句捕获它并处理,要么用 throws 子句声明抛出它,不然编译不会经过。这样的异常通常是由程序的运行环境致使的。由于程序可能运行被运行在各类未知的环境下,而程序员没法干预用户如何使用他编写的程序,因而程序员就应该为这样的异常时刻准备着。如 SQLException,IOException,ClassNotFoundException 等。模块化
下面的代码会演示2个异常类型:ArithmeticException 和 InputMimatchException。前者因为整数除 0 引起,后者是输入的数据不能被转化为 int 类型引起。函数
package com.example; import java. util .Scanner ; public class AllDemo { public static void main (String [] args ) { System . out. println( "----欢迎使用命令行除法计算器----" ) ; CMDCalculate (); } public static void CMDCalculate () { Scanner scan = new Scanner ( System. in ); int num1 = scan .nextInt () ; int num2 = scan .nextInt () ; int result = devide (num1 , num2 ) ; System . out. println( "result:" + result) ; scan .close () ; } public static int devide (int num1, int num2 ){ return num1 / num2 ; } } /***************************************** ----欢迎使用命令行除法计算器---- 0 Exception in thread "main" java.lang.ArithmeticException : / by zero at com.example.AllDemo.devide( AllDemo.java:30 ) at com.example.AllDemo.CMDCalculate( AllDemo.java:22 ) at com.example.AllDemo.main( AllDemo.java:12 ) ----欢迎使用命令行除法计算器---- r Exception in thread "main" java.util.InputMismatchException at java.util.Scanner.throwFor( Scanner.java:864 ) at java.util.Scanner.next( Scanner.java:1485 ) at java.util.Scanner.nextInt( Scanner.java:2117 ) at java.util.Scanner.nextInt( Scanner.java:2076 ) at com.example.AllDemo.CMDCalculate( AllDemo.java:20 ) at com.example.AllDemo.main( AllDemo.java:12 ) *****************************************/
异常是在执行某个函数时引起的,而函数又是层级调用,造成调用栈的,所以,只要一个函数发生了异常,那么它的全部 caller 都会被异常影响。当这些被影响的函数以异常信息输出时,就造成了异常追踪栈。
异常最早发生的地方,叫作异常抛出点。
从上面的例子能够看出,当 devide 函数发生除 0 异常时,devide 函数抛出 ArithmeticExcepton 异常,所以调用它的 CMDCalculate 函数也没法正常完成,所以也发送异常,而 CMDCalculate 的 caller --main 由于 CMDCalculate 抛出异常,也发生了异常,这样一直向调用栈的栈底回溯。这种行为叫作异常的冒泡,异常的冒泡是为了在当前发生异常的函数或者这个函数的 caller 中找到最近的异常处理程序。因为这个例子没有使用任何异常处理机制,所以异常最终由 main 函数抛给 JRE,致使程序终止。
上面的代码不使用异常处理机制,也能够顺利编译,由于2个异常都是非检查异常。可是下面的例子就必须使用异常处理机制,由于异常是检查异常。
代码中选择使用 throws 声明异常,让函数的调用者去处理可能发生的异常。可是为何只 throws 了 IOException呢?由于 FileNotFoundException 是 IOException 的子类,在处理范围内。
@Test public void testException() throws IOException { //FileInputStream的构造函数会抛出FileNotFoundException FileInputStream fileIn = new FileInputStream("E:\\a.txt"); int word; //read方法会抛出IOException while((word = fileIn.read())!=-1) { System.out.print((char)word); } //close方法会抛出IOException fileIn.clos }
在编写代码处理异常时,对于检查异常,有2中不一样的处理方式:使用 try...catch...finally 语句块处理它。或者,在函数签名中使用 throws 声明交给函数调用者 caller 去处理。
try...catch...finally 语句块
try{ //try块中放可能发生异常的代码。 //若是执行完try且不发生异常,则接着去执行finally块和finally后面的代码(若是有的话)。 //若是发生异常,则尝试去匹配catch块。 }catch(SQLException SQLexception){ //每个catch块用于捕获并处理一个特定的异常,或者这异常类型的子类。Java7中能够将多个异常声明在一个catch中。 //catch后面的括号定义了异常类型和异常参数。若是异常与之匹配且是最早匹配到的,则虚拟机将使用这个catch块来处理异常。 //在catch块中可使用这个块的异常参数来获取异常的相关信息。异常参数是这个catch块中的局部变量,其它块不能访问。 //若是当前try块中发生的异常在后续的全部catch中都没捕获到,则先去执行finally,而后到这个函数的外部caller中去匹配异常处理器。 //若是try中没有发生异常,则全部的catch块将被忽略。 }catch(Exception exception){ //... }finally{ //finally块一般是可选的。 //不管异常是否发生,异常是否匹配被处理,finally都会执行。 //一个try至少要有一个catch块,不然, 至少要有1个finally块。可是finally不是用来处理异常的,finally不会捕获异常。 //finally主要作一些清理工做,如流的关闭,数据库链接的关闭等。 }
注:
有的编程语言当异常被处理后,控制流会恢复到异常抛出点接着执行,这种策略叫作:resumption model of exception handing(恢复式异常处理模式)
而 Java 则是让执行流恢复处处理了异常的 catch 块后接着执行,这种策略叫作:termination model of exception handing(终结式异常处理模式)
public static void main(String[] args){ try { foo(); }catch(ArithmeticException ae) { System.out.println("处理异常"); } } public static void foo(){ int a = 5/0; //异常抛出点 System.out.println("为何还不给我涨工资!!!"); //////////////////////不会执行 }
throws 函数声明
throws 声明:若是一个方法内部的代码会抛出检查异常(checked exception),而方法本身又没有彻底处理掉,则 Java 保证你必须在方法的签名上使用 throws 关键字声明这些可能抛出的异常,不然编译不经过。
throws 时另外一种处理异常的方式,它不一样于 try...catch...finally,throws 仅仅时将函数中可能出现的异常向调用者声明,而本身则不具体处理。
采起这种异常处理的缘由多是:方法自己不知道如何处理这样的异常,或者说让调用者处理更好,调用者须要为可能发生的异常负责。
finally 块无论异常是否发生,只要对应的 try 执行了,则它必定也执行。只有一种方法可让 finally 块不执行:System.exit(0)
。所以 finally 块一般用来作资源释放操做:关闭文件,关闭数据库链接等等。
注:
程序员也能够经过 throw 语句手动显示的抛出一个异常。throw 语句的后面必须是一个异常对象。
throw 语句必须写在函数中,执行 throw 语句的地方就是一个异常抛出点,它和有 JRE 自动造成的异常抛出点没有任何差异。
public void save(User user) { if(user == null) throw new IllegalArgumentException("User对象为空"); //...... }
在一些大型的,模块化的软甲开发中,一旦一个地方发生异常,则如骨牌效应通常,将致使一连串的异常。假设 B 模块完成本身的逻辑须要调用 A 模块中的方法,若是 A 模块发生异常,则 B 也将不能完成而发生异常,可是 B 在抛出异常时,会将 A 的异常信息掩盖掉,这将使得异常的根源信息丢失。异常的链化能够将多个模块的异常串联起来,使得异常信息不会丢失。
异常链化:以一个异常对象为参数构造新的异常对象。新的异常对象将包含先前异常的信息。这项技术主要是异常类的一个带 Throwable 参数的函数来实现的。这个看成参数的异常,咱们叫它根源异常(cause)。
查看 Throwable 类源码,能够发现里面有一个 Throwable 字段 cause,就是它保存了构造时传递的根源异常参数。这种设计和链表的节点类设计一模一样,所以造成链也是天然的了。
public class Throwable implements Serializable { private Throwable cause = this; public Throwable(String message, Throwable cause) { fillInStackTrace(); detailMessage = message; this.cause = cause; } public Throwable(Throwable cause) { fillInStackTrace(); detailMessage = (cause==null ? null : cause.toString()); this.cause = cause; } //........ }
若是要自定义异常,则扩展 Exception 类便可,所以这样的自定义异常都属于检查异常。若是要自定义非检查异常,则扩展自 RuntimeException。
自定义的异常应该老是包含以下的构造函数:
下面是 IOException 类的完成源代码,能够借鉴。
public class IOException extends Exception { static final long serialVersionUID = 7818375828146090155L; public IOException() { super(); } public IOException(String message) { super(message); } public IOException(String message, Throwable cause) { super(message, cause); } public IOException(Throwable cause) { super(cause); } }
class Father { public void start() throws IOException { throw new IOException(); } } class Son extends Father { public void start() throws Exception { throw new SQLException(); } } /**********************假设上面的代码是容许的(实质是错误的)***********************/ class Test { public static void main(String[] args) { Father[] objs = new Father[2]; objs[0] = new Father(); objs[1] = new Son(); for(Father obj:objs) { //由于Son类抛出的实质是SQLException,而IOException没法处理它。 //那么这里的try。。catch就不能处理Son中的异常。 //多态就不能实现了。 try { obj.start(); }catch(IOException) { //处理IOException } } } }
首先一个不容易理解的事实:在 try 块中即使有 return,break,continue 等改变执行流的语句,finally 也会执行。
public static void main(String[] args) { int re = bar(); System.out.println(re); } private static int bar() { try{ return 5; } finally{ System.out.println("finally"); } } /*输出: finally */
也就是说:try...catch.finally 中的 return 只要能执行,就都执行了,它们共同向同一个内存地址(假设地址是 0x80)写入返回值,后执行的将覆盖先执行的数据,而真正被调用者取的返回值就是最后一次写入的。那么,按照这个思想,下面的这个例子也就不难理解了。
finally 中的 return 会覆盖 try 或者 catch 中的返回值。
public static void main(String[] args) { int result; result = foo(); System.out.println(result); /////////2 result = bar(); System.out.println(result); /////////2 } @SuppressWarnings("finally") public static int foo() { trz{ int a = 5 / 0; } catch (Exception e){ return 1; } finally{ return 2; } } @SuppressWarnings("finally") public static int bar() { try { return 1; }finally { return 2; } }
finally 中的 return 会抑制(消灭)前面 try 或者 catch 块中的异常
class TestException { public static void main(String[] args) { int result; try{ result = foo(); System.out.println(result); //输出100 } catch (Exception e){ System.out.println(e.getMessage()); //没有捕获到异常 } try{ result = bar(); System.out.println(result); //输出100 } catch (Exception e){ System.out.println(e.getMessage()); //没有捕获到异常 } } //catch中的异常被抑制 @SuppressWarnings("finally") public static int foo() throws Exception { try { int a = 5/0; return 1; }catch(ArithmeticException amExp) { throw new Exception("我将被忽略,由于下面的finally中使用了return"); }finally { return 100; } } //try中的异常被抑制 @SuppressWarnings("finally") public static int bar() throws Exception { try { int a = 5/0; return 1; }finally { return 100; } } }
finally 中的异常会覆盖(消灭)前面 try 或者 catch 中的异常
class TestException { public static void main(String[] args) { int result; try{ result = foo(); } catch (Exception e){ System.out.println(e.getMessage()); //输出:我是finaly中的Exception } try{ result = bar(); } catch (Exception e){ System.out.println(e.getMessage()); //输出:我是finaly中的Exception } } //catch中的异常被抑制 @SuppressWarnings("finally") public static int foo() throws Exception { try { int a = 5/0; return 1; }catch(ArithmeticException amExp) { throw new Exception("我将被忽略,由于下面的finally中抛出了新的异常"); }finally { throw new Exception("我是finaly中的Exception"); } } //try中的异常被抑制 @SuppressWarnings("finally") public static int bar() throws Exception { try { int a = 5/0; return 1; }finally { throw new Exception("我是finaly中的Exception"); } } }
上面的3个例子都异于常人的编码思惟,所以建议: