异常处理机制
异常处理机制可以使程序中的异常处理代码和正常业务代码分离。
多个catch块之后可以跟一个finally,finally块用于回收try块里打开的物理资源,异常机制会保证finally块总是被执行。
JAVA提出一种假设,如果程序顺利完成,则一切正常,把系统的业务实现代码放在try块中,把所有的异常处理逻辑放在catch块中进行处理。
JAVA运行时环境(JRE)收到异常对象时,会寻找能处理该异常的catch块,如果找到了合适的catch块,就将该异常对象交给该catch块处理,这个过程被称为捕获(catch)异常;如果JAVA运行时环境找不到捕获异常的catch块,那么运行时环境将终止,JAVA程序也将退出。
不管程序代码是否位于try块中,甚至包括catch块中的代码,只要执行该代码块时出现了异常,系统总会自动生成一个异常对象。如果没有为这个异常定义任何catch块,那么运行时环境无法找到处理该异常的catch块,程序就此退出。
每个catch块都是专门用于处理该异常类及其子类的异常实例,try块之后可以有多个catch块,但try执行一次,最多有一个catch块运行,绝不可能有多个catch块运行。
JAVA中非正常情况分为Exception和Error两种,Error错误,一般是指和虚拟机有关的问题——系统崩溃,虚拟机崩溃,动态链接失败等,这类错误无法被捕获或者修复,将导致应用程序中断。通常应用程序无法处理这些错误,因此应用程序不应该用catch块来捕获Error对象。
当试图调用一个null对象的实例方法或实例变量时,就会引发NullPointerException异常。
在异常捕获时,一定要先捕获小异常,后捕获大异常。
从JAVA7开始,一个catch块可以捕获多种类型的异常,多种类型异常之间用竖线(|)分开,多种异常的异常变量有隐式的final修饰,因此程序不能为异常变量重新赋值。
当Java运行时决定调用某个catch块来处理该异常对象时,会将异常对象赋值给catch块的异常参数,程序即可通过该参数来获得异常的相关信息。
finally块
不管try中有没有异常,不管哪个catch块被执行,甚至在try块或catch块中执行了return,finally块总会被执行。
如果没有try块,则后面不能出现catch块和finally块。但如果出现try块,则catch块和finally块必须出现一个。多个catch块必须位于try块之后,finally块必须位于所有catch块之后。
return语句会强制方法结束,但一定会先执行finally块代码,如果将return 改为System.exit(1)语句退出虚拟机,则finally块将失去执行的机会。
不要在finally块使用return、throw等会导致方法终止的语句,一旦在finally块中使用这些语句,会导致try,catch块中的return、throw语句失效。
public class FinallyFloatTest {
public static void main(String []args)throws Exception
{
boolean a=test();
System.out.println(a);
}
public static boolean test()
{
try
{
return true;
}
finally
{
return false;
}
}
}
运行结果是false,也就是,try块中的return语句失效了。
JAVA7中指定关闭资源的try语句
JAVA7增强了try语句功能——允许try关键字后紧跟一对圆括号(不再是大括号),圆括号中可以声明并初始化一个或多个资源,这里的资源指的是那些必须在程序结束时显示关闭的资源(如数据库连接,网络连接),try语句结束后这些资源将自动关闭,
为了保证try语句可以正常关闭资源,这些资源必须实现AutoCloseable或Closeable语句,实现这两个接口就必须实现close()方法。
自动关闭资源的try语句等于自带finally块,所以其后面可以不加catch块和finally块。
Checked异常和Runtime异常体系
Checked异常是JAVA特有的。JAVA认为Checked异常都是可以被处理(修复)的异常,所以JAVA程序必须显示处理Checked异常,如果程序没有处理Checked异常,该程序在编译时就会发生错误,无法通过编译。
而Runtime异常更为灵活,无需显式抛出,如果程序需要捕获Runtime异常,也可以使用try catch来实现。
throws声明抛出的思路是——当前方法不知道该如何处理该类型的异常,该异常就应该有上一级的调用者处理,如果主函数也不知道该如何处理,也可以使用throws声明抛出异常,将该异常传给JVM来处理。JVM对异常的处理方式是打印异常跟踪栈的信息,并终止程序运行。
throws可以抛出多个异常类,异常类之间以逗号隔开
如果某一段代码中调用了一个带throws声明的方法,那么表面该程序希望其调用者处理这个异常,该方法要么放在一个try块中,要么放在另一个带有throws异常的方法里。
子类方法声明抛出的异常类型应与父类声明抛出的异常类型相同(或者是其子类),子类抛出的异常不允许比父类声明抛出的异常多
在方法中显式声明Checked异常会导致该方法签名与异常耦合,若该方法是重写父类的方法,那么该方法抛出的异常还受父类异常的限制。
当使用Runtime一次时,程序无需在方法中声明Checked异常,一旦发生自定义异常,直接抛出即可。
如果程序需要在合适的地方捕获并处理异常,使用try catch语句即可。
使用throw抛出异常
throw语句抛出的不是一个异常类,而是一个异常实例,而且每次只能抛出一个异常实例。
如果throw语句抛出的是Checked异常,那么该throw语句要么处于try块中,要么处于一个带throws声明的方法中;如果throw抛出的是Runtime异常,那么无需放在try块中,也无需放在带throws声明的方法中——程序可以显示地使用try catch语句捕获该异常,也可以完全不理会该异常,将该异常交给方法调用者来处理。
用户自定义异常都应该继承自Exception类,如果希望自定义Runtime异常,这应该继承RuntimeException基类。定义异常时通常需要提供两个构造器:一个无参构造器;另一个是带字符串的构造器——该字符串作为异常的描述信息(也就是异常对象getMessage()方法的返回值)
public class AuctionException extends Exception{
public AuctionException()
{}
public AuctionException(String str)
{
super(str);
}
}
如上述代码所示,通过super()方法可以将字符串参数传给异常对象的message属性,该message属性就是该异常对象的详细描述信息。
异常类名应该能准确的描述异常类型。
实际使用异常机制时,一个方法可以只处理部分异常,然后将异常再次抛出,让方法的调用者处理剩余的异常(catch语句块中再抛出异常)
从JAVA7开始编译器会进行细致的检查——编译器会检查throw语句抛出的异常的实际类型,因此可以抛出更准确的异常,而不是像之前那样粗暴的抛出。
把一个异常捕获然后抛出另一个异常,并把原始异常信息保留下来是一种典型的链式处理(23种设计模式之一:职责链模式),也被称为异常链。
从JDK1.4之后,Throwable基类有了一个可以接受Exception参数的方法,所以可以使用以下代码:
public class SalException extends Exception {
public SalException(){}
public SalException(String msg)
{
super(msg);
}
public SalException(Throwable t)
{
super(t);
}
}
JAVA的异常跟踪栈
根据printStackTrace()方法输出结果,开发者可以找到异常的源头,并跟踪到异常一路触发的过程。
打印信息的顺序是——最开始触发异常的地方,然后异常向外传播,传给了该方法的调用者...最后一只传到main方法。如果main方法也没有处理该异常,那么JVM会终止该程序,并打印异常的跟踪栈信息。
第一行信息详细显示了异常的类型和异常的详细消息,然后是跟踪栈记录程序中所有的异常发生的位置,显示被调用方法中执行终止的位置,并标明类、类中的方法名、与故障点对应的文件的行。
异常处理规则
不要过度使用异常
对于普通错误,应书写处理错误的代码,只有对外部的、不可确定、不可预知的运行时错误才使用异常。
异常机制的效率要低于正常的流程控制效率,所以不要使用异常处理来替代正常的程序流程控制。
不要使用过于庞大的try块
应该吧大块的try分割成多个可能出现异常的程序段落,并把它们放在单独的try块中,分别捕获和处理。
不要忽略捕获到的异常
捕获到异常后应该处理并修复这个异常,catch块不要为空,也不要仅仅是打印该异常。