Java的异常机制主要依赖于try、catch、finally、throw、throws 五个关键字。Java中,异常被分为两种,Checked异常和Runtime异常,Java认为Checked异常可以在编译阶段被处理的异常,所以它强制程序处理所有的 Checked 异常,而 Runtime 异常则无须处理。
如果执行 try 块里的业务逻辑代码时出现异常,系统自动生成一个异常对象,该异常对象被提交给 Java 运行时环境,这个过程被称为抛出异常。当 Java 运行时环境收到异常对象时,会寻找能处理该异常对象的 catch 块,如果找到合适的 catch 块,则把该异常对象交给该 catch 块处理,这个过程被称为捕获异常。如果 Java 运行时环境照不到捕获异常的 catch 块,则运行时环境终止,Java 程序也将退出。
Java把所有的非正常情况分成两种:异常(Exception)和错误(Error),它们都继承 Throwable 父类。 Error错误: 一般指与虚拟机相关的问题,如系统崩溃、虚拟机错误、动态链接失败等,这种错误无法恢复或不可能捕获,将导致应用程序中的。通常程序无法处理这类错误,因此应用程序不应该试图使用 catch 块来捕获 Error 对象。
注意: 编写程序时,捕获异常如果需要使用 Exception 进行捕获,那么这个应当写在 catch 的最后一个,因为如果将这个异常排在前面,那么程序遇到异常时将直接进入这个 catch 块,而排在它后面的 catch将永远也不会得到执行的机会。这是因为所有的异常对象都是 Exception 或其子类的实例。实际上,进行异常捕获时应当将所欲的父类异常 catch 块排在子异常 catch 块后面,否则将会引起编译错误。
如果程序需要在 catch 块中访问异常对象的相关信息,则可以通过访问 catch 块的后异常参数来获取。
getMessage() :返回改异常的详细描述字符串。 printStackTrace():将该异常的跟踪栈信息输出到标准错误输出。 printStackTrace(PrintStream s):将该异常的跟踪栈信息输出到指定输出流。 getStackTrace():返回改异常的跟踪栈信息。
有些时候,程序在 try 块中打开了一些物理资源,这些资源都必须显示回收。然而不管在 try 块或者catch块中,都有可能无法执行回收代码,导致资源回收失败。为了保证一定能回收 try 中打开的物理资源,异常处理机制提供了 finally 块,不管 try 块中代码是否出现异常,也不管哪一个 catch 块被执行,甚至在 try 块中或者 catch 块中 执行了 return 语句,除非,在 try 块中或者 catch 块中调用了退出虚拟机的方法,否则finally 语句总是会执行。
try{ //业务实现代码 }catch (IndexOutOfBoundsException e){ //异常处理代码1 }catch (NullPointerException e){ //异常处理代码2 }finally { //资源回收块 }异常处理语句中 try 块是必须,但是 catch 块和 finally 块都是可选的,但至少出现其中之一,也可以同时出现且 finally 块必须位于所有 catch 块之后。 通常情况下,不要在 finally 块中执行 return 或者 throw 语句,如果使用了,将会导致 try 块、catch 块中的 return、 throw 失效。
在 Java 7 以前,程序的关闭资源代码异常的臃肿,Java 7 之后改变了这种状况。Java 7 增强了 try 语句的功能,它允许在 try 关键字后面紧跟一对圆括号,圆括号可以声明、初始化一个或多个资源(必须是在程序结束时需要显示关闭的资源,如数据库连接,网络连接等),try 语句会在语句结束时自动关闭这些资源。需要指出的是,使用这种方式进行关闭资源,这些资源的实现类必须实现 AutoCloseable 或者 Closeable 接口,实现这两个接口就必须实现 close() 方法。
//声明、初始化两个可以关闭的资源,try 语句会自动关闭这两个资源 try(BufferedReader br = new BufferedReader(new FileReader("a.txt")); PrintStream ps = new PrintStream(new FileOutputStream("b.txt"))){ // 使用这两个资源 System.out.println(br.readLine()); ps.print("请叫我龙傲天!!!"); }自动关闭资源的 try 语句相当于包含了 隐式的 finally 块,因此这个语句既可以没有 catch,也可以没有 finally 块。当然,如果程序需要,自动关闭资源的 try 语句后也可以带多个 catch 块和 finally 块。
如果当前方法不知道如何处理当前产生的异常,该异常应该由上一级调用者处理,如果 main 方法也不知道如何处理这种类型的异常,也可以使用 throws 抛出,该异常有 JVM 处理。JVM 处理异常的方式是,打印异常的跟踪栈信息,并中止程序运行,这就是程序遇到异常后自动结束的原因。 语法格式:
throws ExceptionClass1,ExceptionClass12 public static void test() throws FileNotFoundException { FileInputStream fis = new FileInputStream("a.tet"); }上面的方法中,FileNotFoundException 异常被抛出,将会交由调用该方法的调用方处理,如果调用无法处理,则继续向上传递,知道传递到 JVM 中。
当程序出现错误时,系统会自动抛出异常,除此之外,Java 也允许程序自行抛出异常,使用 throw 关键字来完成。
语法格式:
throw ExceptionInstance; public static void test(int a ) { if (a > 0){ //自行抛出的异常,既可以显示的进行捕获,也可以完全不理会该异常,把该异常交有调用者处理。 throw new RuntimeException("a 的值大于0,不符合要求!"); } }throw 语句可以单独使用,throw 语句抛出的不是异常类,而是一个异常实例,而且每次只能抛出一个异常实例。不管是程序自己抛出的异常,还是我们手动抛出的异常,Java 运行时环境对异常的处理没有任何差别。
自定义异常都应该继承 Exception 基类,如果希望自定义 Runtime 异常,则应该继承 RuntimeException 基类。定义异常类时,通常需要提供两个构造器,一个是无参的构造器,另一个是带一个字符串参数的构造,这个字符串对象将作为该异常对象的描述信息,也就是异常对象的 getMessage() 方法的返回值。
public class testException extends Exception{ //无参数的构造器 public testException() { } //带参数的构造器 public testException(String msg){ super(msg); } }异常处理有两种方式:
在异常出现的方法中捕获并处理异常,该方法的调用者将不能再次捕获该异常。该方法中声明抛出的异常,将该异常完全交由调用方处理。但当出现一种情况,即单靠某个方法无法完全处理该异常,必须交由几个方法协作才可以完全处理该异常。也就是说,当出现异常时,出现只对异常进行部分处理,还有些处理需要在该方法的调用者中才可以完成,所以应该再次抛出,让该方法的调用者也能捕获到异常。
public class test{ private double initPrice = 30.0; //需要声明抛出 testException 异常,此异常将会交由调用处理 public void bid(String bidPrice) throws testException { double d = 0.0; try{ d = Double.parseDouble(bidPrice); }catch (Exception e){ //在此处处理该异常信息 //打印异常的跟踪栈信息 e.printStackTrace(); //处理完成最后再次抛出异常,交由调用方 throw new testException("再次抛出异常,将此异常交由调用方处理!"); } } }