JAVA编程思想学习笔记(十三)通过异常处理错误

通过异常处理错误

  • 基本异常
    • 异常参数
  • 捕获异常
    • try块
    • 异常处理程序
  • 异常说明
    • 捕获所有异常
    • java标准异常
      • 特例:RuntimeException
    • 使用finally进行清理
      • 在return中使用finally
      • 缺憾:异常丢失
    • 异常的机制
    • 构造器
    • 异常匹配
    • 异常使用指南

基本异常

异常情形:是指阻止当前方法或作用域继续执行的问题。
把异常情形和普通问题区分很重要:

  • 普通问题:指在当前环境下能得到足够的信息,总能处理这个错误。
  • 异常情形:在当前的环境下无法获得必要的信息解决问题。

异常处理程序:他的任务是将程序从错误状态中恢复,以使程序能要么换一种方式运行,要么继续运行下去。
异常最重要的方面之一就是如果发生问题,他们将不允许程序沿着正常的路径继续下去,异常允许我们强制程序停止运行,并告诉我们出了什么问题,或者强制程序处理问题,并回到稳定状态。

异常参数

所有标准异常类都有两个构造器:一个默认构造器;另一个是接受字符串作为参数,以便把相关信息放入异常对象的构造器。
例子如下:

throw new NullPointerException("t=null");

关键字throw将异常对象抛出,类似于方法里面的return。

捕获异常

监控区域:它是一段可能产生异常的代码,并且后面跟着处理这些异常的代码。

try块

如果在方法内部抛出了异常,这个方法将在抛出异常的过程中结束。要是不希望方法就此结束,可以在方法内设置一个特殊的块来捕获异常。这个块就是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支持终止模型,在这种模型中,将假设错误非常关键,以至于无法返回到异常发生的地方继续执行。一旦异常被抛出,就表明错误以无法挽回,也不能回来继续执行。
  • 恢复模型:异常处理程序的工作是修正错误,然后重新尝试调用出问题的方法,并认为第二次能成功。长久以来都使用恢复模型的异常处理,但最终还是转向了终止模型。恢复模型开始显得很吸引人,但不是很实用。其中的主要原因可能是它所导致的耦合:恢复性处理程序需要了解异常抛出的地点,这势必要包含依赖于抛出位置的非通用性代码。

异常说明

异常说明: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()方法。

java标准异常

Throwable这个java类被用来表示任何可以作为异常被抛出的类,它可以分为两种类型:

  1. Error:用来表示编译时的系统错误(除特殊情况,一般不需要你管)
  2. Exception:是可以被抛出的基本类型,在java类库、用户方法以及运行时故障中都可能抛出Exception型异常。

特例:RuntimeException

属于运行时异常的类型很多,它们会自动被虚拟机抛出,所以不必在说明中将它们列出来,这些异常都是从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()方法。

使用finally进行清理

对于一些代码,可能会希望无论try块中的异常是否抛出,它们都能执行,这时候就可以使用finally子句了。
完整的处理看起来应该是这样的:

try{
}catch(A a1){
}catch(B b1){
}finally{
//无论怎么样,这里的代码都将会被执行。
}

在return中使用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编程思想》中给出的使用指南,由于本人经历还欠火候,并不能很好的进行注释,所以,只是将内容留在这里,不清楚的可以共同讨论。

  1. 在恰当的级别处理问题
  2. 解决问题并且重新调用产生异常的方法
  3. 进行少许修补,然后绕过异常发生的地方继续执行
  4. 用别的数据进行计算,以代替方法预计会返回的值
  5. 把当前运行环境下能做的事情尽量做完,然后把相同的异常重抛到更高层
  6. 把当前运行环境下能做的事情尽量做完,然后把不同的异常抛到更高层
  7. 终止程序
  8. 进行简化
  9. 让类库和程序更安全

你可能感兴趣的:(学习笔记)