异常情形:是指阻止当前方法或作用域继续执行的问题。
把异常情形和普通问题区分很重要:
异常处理程序:他的任务是将程序从错误状态中恢复,以使程序能要么换一种方式运行,要么继续运行下去。
异常最重要的方面之一就是如果发生问题,他们将不允许程序沿着正常的路径继续下去,异常允许我们强制程序停止运行,并告诉我们出了什么问题,或者强制程序处理问题,并回到稳定状态。
所有标准异常类都有两个构造器:一个默认构造器;另一个是接受字符串作为参数,以便把相关信息放入异常对象的构造器。
例子如下:
throw new NullPointerException("t=null");
关键字throw将异常对象抛出,类似于方法里面的return。
监控区域:它是一段可能产生异常的代码,并且后面跟着处理这些异常的代码。
如果在方法内部抛出了异常,这个方法将在抛出异常的过程中结束。要是不希望方法就此结束,可以在方法内设置一个特殊的块来捕获异常。这个块就是try块。
下面用一个例子演示一下异常处理:
class SimpleException extends Exception{}
public class TestYiChang {
public void fun() throws SimpleException{
System.out.println("fun");
throw new SimpleException();
}
public static void main(String[] args) {
// TODO 自动生成的方法存根
TestYiChang t = new TestYiChang();
try{
t.fun();
}catch(SimpleException e){
System.out.println("catch");
}
}
}
在try中产生的异常,会在紧跟在后面的catch中捕获处理。
异常处理理论上有两种基本模型:
异常说明:java提供了语法,使你可以告诉客户端程序猿某个方法会抛出异常的类型,然后客户端程序猿就可以进行相应的处理。它属于声明的一部份,关键字为throws,关键字后面是异常类型,throws紧跟在形式参数列表之后,语法如下:
void fun() throws SimpleException
如果这样写:
void fun()
就代表此方法不会抛出异常,除了从RuntimeException继承的异常。
捕获所有异常是个很容易的事,可以只写一个捕获程序来进行捕获。通过捕获异常类的基类Exception,就可以做到这一点。
下面简单的给出个例子:
catch(Exception e){
System.out.println("Caught an exception");
}
最好把它放在程序列表的最末尾,以防它抢在其他处理程序之前先把异常捕获了。
Exception是所有异常的基类,它继承自Throwable,其中有几个方法比较有用,可以注意一下:
String getMessage()//获取详细信息
String gerLocalizedMessage()//用本地语言表示详细信息
String toString()//返回对Throwable的简单描述,要是有详细信息的话也会包含在内
//下面几个是打印Throwable的调用栈轨迹
void printStackTrace()
void printStackTrace(PrintStream)
void printStackTrace(java.io.PrintWriter)
Throwable fillInStackTrace()//用于在Throwable对象的内部记录栈帧的当前状态,这在程序重新抛出错误或异常的时候很有用
此外也可以使用从Object继承的方法,对于异常来说,getClass()也许是个很好用的方法,他将返回一个表示此对象类型的对象。然后可以使用getName()方法查询这个Class对象包含信息的名称,或者使用只产生类名的getSimpleName()方法。
Throwable这个java类被用来表示任何可以作为异常被抛出的类,它可以分为两种类型:
属于运行时异常的类型很多,它们会自动被虚拟机抛出,所以不必在说明中将它们列出来,这些异常都是从RuntimeException中继承而来,被称为不受检查异常,这种异常属于错误,将被自动捕捉。
既然会被自动捕获,那自己不去管运行时异常会发生什么呢?
可以试下书中给的小例子:
public class NeverCaught {
public static void f(){
throw new RuntimeException("f()");
}
public static void g(){
f();
}
public static void main(String[] args) {
// TODO Auto-generated method stub
g();
}
}
其结果为:
Exception in thread "main" java.lang.RuntimeException: f()
at test.NeverCaught.f(NeverCaught.java:5)
at test.NeverCaught.g(NeverCaught.java:8)
at test.NeverCaught.main(NeverCaught.java:12)
可以看到,这种异常类型,其输出被报告给了System.err。所以如果RuntimeException没有被捕获,直达main中,那么在程序退出前将调用异常的printStackTrace()方法。
对于一些代码,可能会希望无论try块中的异常是否抛出,它们都能执行,这时候就可以使用finally子句了。
完整的处理看起来应该是这样的:
try{
}catch(A a1){
}catch(B b1){
}finally{
//无论怎么样,这里的代码都将会被执行。
}
return会直接返回,而finally必会执行,那么它俩的用一起会执行finally吗?可以执行的话它们的顺序会是什么样的?
下面用个上述例子进行修改后说明:
public class NeverCaught {
public static int f(int i){
try{
System.out.println("i="+i);
return i;
}finally{
System.out.println("this is finally");
i = 10;
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
for(int i=0;i<3;i++){
System.out.println(""+f(i));
}
}
}
运行结果:
i=0
this is finally
0
i=1
this is finally
1
i=2
this is finally
2
可以很清晰的看出来,它们的顺序:return -> finally->返回的函数
用某些特殊的方式使用finally子句的时候,会发生异常丢失。比如在finally抛出异常的情况,就会将try块中的异常取代,另外使用return也会导致异常丢书。
综合来讲就是不要在finally中出现返回,抛出等操作。
下面看个简单的异常丢失例子:
public class NeverCaught {
public static void main(String[] args) {
// TODO Auto-generated method stub
try{
throw new RuntimeException();
}finally{
System.out.println("this is finally");
// return;
}
}
}
输出:
this is finally
Exception in thread "main" java.lang.RuntimeException
at test.NeverCaught.main(NeverCaught.java:18)
但是要是有ruturn的话:
public class NeverCaught {
public static void main(String[] args) {
// TODO Auto-generated method stub
try{
throw new RuntimeException();
}finally{
System.out.println("this is finally");
return;
}
}
}
输出:
this is finally
显然,异常不见了!
当覆盖方法时,只能抛出在基类方法的异常说明里列出的那些异常。这个限制很有用,因为这意味着,当基类使用的代码应用到其派生类对象的时候,一样能够工作。
尽管在继承过程中,编译器会对异常说明做强制要求,但异常本身并不属于方法类型的一部分,方法类型是由方法的名字与参数的类型组成的,因此不能用异常说明来重载方法。
比如异常类型A和B:
void fun() throws A{}
void fun() throws B{}
这样子的两个fun()不能重载。
此外,一个出现在基类方法的异常说明中的异常,不一定会出现在派生类方法的异常说明里。
有一点很重要,即你要时刻的询问自己,“如果异常发生了,所有的东西都能被正确的清理吗?”,在涉及到构造器的时候,问题就出现了,构造器会把对象设置成安全的初始状态,但还会有别的动作,比如打开一个文件,问题就出现了。finally会在每次完成构造器都会执行一遍,有时候你打开文件失败,抛出异常,这时候没打开文件却要执行close,肯定是不行的,因此它实在不该是调用close()的地方,这时候要怎么办呢?
很简单,在try块中嵌套try finally 就可以了,这样当发生异常的时候,内部的try finally中执行close,如果打开失败就会,跳过内部try块,直接执行外部catch。
抛出异常的时候,异常处理系统会按照代码的书写顺序找出“最近”的处理程序,找到匹配的处理程序后,它就会认为异常得到处理,不会再继续查找。
所以在进行捕获的时候,应该先派生类,再基类,从下往上的顺序进行捕获,这样异常就可以被最合适的处理程序捕获。
下面是《JAVA编程思想》中给出的使用指南,由于本人经历还欠火候,并不能很好的进行注释,所以,只是将内容留在这里,不清楚的可以共同讨论。