try块与if语句不一样, try块后的花括号不可以省略,即使只有一行代码,也不能省略花括号。与之类似的是 catch块后的花括号也不可以省略。
try块里声明的变量是代码块内局部变量,它只在try块内有效,在catch块中不能访问。
不管try块中的代码是否出现异常,也不管哪一个catch块被执行,甚至在try块或者catch块中执行了return语句,finally块总会被执行。
异常处理语法结构中只有try块是必需的,也就是说,如果没有try块,则不能有后面的catch块和finally块,catch块和finally块是可选的,但 catch块和finally块至少出现其中之一,也可以同时出现多个catch块,捕获父类异常的catch块必需位于捕获子类异常后面,finally块必需位于所有catch块之后。
虽然return语句也强制方法结束,但一定会先执行finally块里的代码。如果在异常处理代码中使用System.exit(1)语句来退出虚拟机,则finally块将失去执行的机会。
通常情况下,不要在finally块中使用return或者throw等导致方法终止的语句,一旦使用将会导致try块、catch块中的retrun、throw语句失效。
当java程序执行try块、catch块时遇到了return 或throw语句,这两个语句都会导致该方法立即结束,但是系统执行这两个语句并不会结束该方法,而是去勋章该异常处理流程中是否包含finally块,如果没有程序立即执行return或throw语句,方法终止; 如果有finally块,系统立即开始执行finally块——只有当finally块执行完成后,系统才会再次跳回来执行try块、catch块里的return或throw语句;如果finally块里也使用return或throw等导致方法终止的语句,finally块已经终止了方法,系统将不会调回去执行try块、catch块里的任何代码 。
下面是一道面试题
public class ThreeException extends Exception{ } public class TestClass { static int count = 0; public static void main(String[] args) { while(true){ try{ if(count++==0) throw new ThreeException(); System.out.println("No Exception"); }catch(ThreeException e){ System.err.println("ThreeException"); }catch(Exception e){ System.err.println("Exception"); }finally{ System.err.println("Finally"); if(count==2) break; } } } }
打印结果:
ThreeException
Finally
No Exception
Finally
Java异常被分为两大类:checked异常和Runtime异常(运行时异常)。所有RuntimeException类及其子类的实例被称为Runtime异常;不是RuntimeException类及其子类的异常实例则被称为checked异常。
对于checked异常的处理方式有如下两种:
1、当前方法明确知道如何处理该异常,程序应该使用try...catch块来捕获该异常,然后在对应的catch块中修复该异常
2、当前方法不知道如何处理这种异常,应该在定义该方法时声明抛出该异常。
使用throws声明抛出异常的思路是,当前方法不知道如何处理这种类型的异常,该异常应该由上一级调用者处理,如果main方法也不知道如何处理这种类型的异常,也可以 使用throws声明抛出异常,该异常交给JVM处理。
JVM对异常处理的方法是打印异常的跟踪栈信息,并中止程序运行,这就是前面程序在遇到异常后自动结束的原因。
如果某段代码中调用了一个带throws声明的方法,该方法声明抛出了Checked异常,则表明该方法希望它的调用者来处理该异常。也就是说,调用该方法时要么放在try块中显示捕获该异常,要么放在另一个带throws声明抛出的方法中。
使用throws声明抛出异常时有一个限制,就是方法重写时“两小”中的一条规则: 子类方法声明抛出的异常类型应该是父类方法声明抛出的异常类型的子类或相同,子类方法声明抛出的异常不允许比父类方法声明抛出的异常多。
如果需要在程序中自行抛出异常,则应该使用throw语句,throw语句可以单独使用,throw语句抛出的不是异常类,而是一个异常实例,而且每次只能抛出一个异常实例。
当java运行时接受到用户自行抛出的异常时,同样会中止当前的执行流,跳到该异常对应的catch块,有该catch块来处理该异常。也就是说,不管是系统自动抛出异常,还是程序员手动抛出的异常,java运行时环境对异常的处理没有任何差别。
package com.hb.exception; public class ThrowTest { public static void main(String[]args){ System.out.println("ddd"); try{ //调用声明抛出Checked异常的方法,要么显示捕获该异常 //要么在main方法中再次声明抛出 throwChecked(2); }catch(Exception e){ System.out.println(e.getMessage()); } //调用声明抛出Runtime异常的方法既可以显示捕获该异常,也可以不理会该异常 throwRuntime(3); } public static void throwChecked(int a) throws Exception{ if(a > 0){ //自行抛出Exception 异常 //该代码必须处于try块里,或出于带throws声明的方法中 throw new Exception("a的值大于0,不符合要求"); } } public static void throwRuntime(int a){ if(a > 0){ // 自行抛出RuntimeException异常,既可以显示捕获该异常 // 也可完全不理会该异常,把该异常交给该方法调用者处理 throw new RuntimeException("a的值大于0,不符合要求"); } } }
用户自定义异常都应该继承Exception基类,如果希望自定义Runtime异常,则应该继承RuntimeException基类。定义异常类时通常需要提供两个构造器:一个是无参数的构造器;另一个是带一个字符串参数的构造器,这个字符串将作为该异常对象的描述信息(也就是异常对象的getMessage()方法返回的值)。
public class MyException extends Exception{ //无参数的构造器 public MyException(){} //带一个字符串参数的构造器 public MyException(String msg){ super(msg); } }
如果需要自定义Runtime异常,只需要将继承类Exception改为RuntimeException基类即可。
虽然printStackTrace()方法可以很方便地用于追踪异常的发生情况,可以用它来调试程序,但是在最后发布的程序中,应该避免使用它;而应该对捕获的异常进行适当的处理,而不是简单地将异常的跟踪栈信息打印出来。
必须指出:异常处理机制的初衷是将不可预料的异常处理代码和正常的业务逻辑处理分离,因此绝不要使用异常处理来替代正常的业务逻辑判断。
异常机制的效率比正常流程控制效率差,所以不要使用异常处理来替代正常的程序流程控制。
把捕获一个异常然后接着抛出另一个异常,并把原始异常信息保存下来是一种典型的链式处理,也被称为“异常链”。
这种把原始异常信息隐藏起来,仅向上提供必要的异常提示信息的处理方式,可以保证底层异常不会扩散到表现层,可以避免向上暴露太多的实现细节,这完全符合面向独享的封装原则
不要使用过于庞大的try块:
try块里的代码过于庞大导致业务过于复杂,会造成try块中出现异常的可能性大大增加,从而导致分析异常原因的难度也加大,紧随后面的catch块才可以针对不同的处理逻辑关系难度也加大,反而增加了编程复杂度。
正确做法是将大块的try块分割成多个可能出现异常的程序段落,并把他们房子单独的try块中,从而分别捕获并处理异常。