Thinking in Java---异常处理机制
java的异常处理机制能够使程序有极好的容错性,让程序更加的健壮.所谓的异常,就是指的阻止当前方法或作用域继续运行的问题,,当程序运行时出现异常时,系统就会自己主动生成一个Exception对象来通知程序.这样就极大的简化了我们的工作.
当然java的异常对象有非常多种,以下这幅图显示了java异常类的继承体系.
从图片中能够看到java将全部的非正常情况分成了两种: 异常(Exception)和错误(Error),他们都继承Throwable父类.Error错误通常是指与虚拟机相关的问题,如系统崩溃,虚拟机错误等.这样的错误无法恢复或不可捕获,将导致应用程序中断,通常应用程序无法处理这些错误,因此也不应该试图用catch来进行捕获.而对于Exception我们是能够进行捕获并处理的.以下从几个方面对异常处理机制进行介绍.
一.异常处理的一般格式
try{
///可能会抛出异常的代码
}
catch(Type1 id1){
//处理Type1类型异常的代码
}
catch(Type2 id2){
///处理type2类型异常的代码
}
try块中放置可能会发生异常的代码(可是我们不知道详细是哪种异常).假设异常发生了,try块抛出系统自己主动生成的异常对象,然后异常处理机制将负责搜寻參数与异常类型相匹配的第一个处理程序,然后进行catch语句运行(不会在向下查找).看起来和switch语句比較相似.除了上面列出的那些异常类,事实上我们也能够自定义异常,然后处理他们.定义异常最重要的是异常类的名字,最好做到望名知义.另外异常处理中经常出现的throws和throw这两个关键字;throws表示的是当前程序不处理这个异常将其抛给调用这个程序的上一级程序运行,假设是main()方法则传给JVM虚拟机处理.throw表示的是显式的抛出一个异常对象.以下的代码中用到了上面提到的知识点.
///自定义一个简单的异常类,仅仅须要声明以下继承关系
///也能够自己编写构造器,但这不是必要的
public class SimpleException extends Exception{
///public SimpleException();
///public SimpleException(String str){
///super(str)};
}
package lkl;
import java.util.Scanner;
public class ExceptionTest {
///声明该程序不处理SimpleException异常而是将其丢出
public void f() throws SimpleException{
System.out.println("Throws SimpleException from f()");
///丢出一个SimpleException异常对象
throw new SimpleException();
}
///多个try语句会依次运行,即使上一个try语句发生异常,下一个try语句任然会运行
public static void main(String[] args){
ExceptionTest et =new ExceptionTest();
try{
et.f();
}///处理try中丢出的SimpleException异常
catch (SimpleException sn){
System.out.println("Catch it");
//调用异常类的printStackTrace()方法,默认是打印到标准错误流的
///如今我们显示指定输出到标准输出流
///会打印从方法调用处直到异常抛出处的方法调用序列,栈顶是最后一次调用的而栈底是第一次掉用的
sn.printStackTrace(System.out);
}
try{
int[] a= new int[10];
Scanner sc = new Scanner(System.in);
System.out.println("请输入k: ");
int k=sc.nextInt();
sc.close();
System.out.println(a[k]);
}
///处理数组下标越界的异常
catch(ArrayIndexOutOfBoundsException ae){
System.out.println("Catch ArrayIndexOutOfBoundsException");
ae.printStackTrace(System.out);
}
}
}
二.打印异常信息
异常类的基类Exception中提供了一组方法用来获取异常的一些信息.所以假设我们获得了一个异常对象,那么我们就能够打印出一些实用的信息,最经常使用的就是void printStackTrace()这种方法,这种方法将返回一个由栈轨迹中的元素所构成的数组,当中每一个元素都表示栈中的一帧.元素0是栈顶元素,而且是调用序列中的最后一个方法调用(这个异常被创建和抛出之处);他有几个不同的重载版本号,能够将信息输出到不同的流中去.以下的代码显示了如何打印主要的异常信息:
package lkl;
///測试异常类Exception的经常用法
public class ExceptionMethods {
public static void main(String[] args){
try{
throw new Exception("My Exception");
}///通过捕获异常类的基类Exception就能够捕获全部类型的异常
catch(Exception e){
System.out.println("Caught Exception");
System.out.println("getMessage(): "+e.getMessage());
System.out.println("getLocalizedMessage(): "+e.getLocalizedMessage());
System.out.println("toString(): "+e.toString());
System.out.println("printStackTrace(): ");
e.printStackTrace(System.out);
}
}
}
三.又一次抛出异常与异常链
有时候须要将捕获的异常又一次抛出,这样在语法上是非常easy的.但问题是假设直接抛出的话,这个异常对象中信息将仍然是原来异常对象中的信息.假设我们想要跟新一下这个信息,我们能够调用fillInStackTrace()方法,这种方法返回一个Throwable对象,它是通过将当前调用栈信息填入原来那个对象而得到的.调用fillStackTrace()的那一行就成了异常的新的发生地.假设我们在捕获了异常之后抛出了第二种异常,那么原来异常发生点的信息会丢失,得到与fillInStackTrace()一样的方法.
所谓的异常链指的是在捕获一个异常后抛出还有一个异常,而且希望把原始异常的信息给保留下来.java中的Throwable子类在构造器中都能够接受一个cause(因由)对象作为參数.这个參数事实上就是原先的异常对象,这样通过把原始异常传递给新的异常,使得即使在当前位置创建并抛出了新的异常,也能够通过异常链追踪到异常最初发生的位置.可是在Throwable的子类中,仅仅对Error,Exception,RuntimeException这三个子类提供了带cause因子的构造器,假设要把其他类型的异常链接起来,就应该使用initCause()方法而不是构造器了.
四.使用finally进行清理
引入finally语句的原因是我们希望一些代码总是能得到运行,不管try块中是否抛出异常.这样异常处理的基本格式变成了以下这样:
try{
///可能会抛出异常的代码
}
catch(Type1 id1){
//处理Type1类型异常的代码
}
catch(Type2 id2){
///处理type2类型异常的代码
}
finally{
///总是会运行的代码
}
事实上catch和finally语句也能够仅仅有一个出现.以下的代码显示了finally语句总能运行
package lkl;
public class FinallyTest extends Exception{
public static int count=0;
public static void main(String[] args){
while(true){
try{
//不管是不是抛出异常,finally语句都会运行
if(count++==0){
throw new MyException();
}
System.out.println("No exception");
}
catch(MyException me){
System.out.println("MyException");
}
finally{
System.out.println("In finally clause");
if(count==2) break;
}
}
}
}
以下这份代码说明就算在try中有return语句,finally语句仍会得到运行,看起来就像有多个返回点.
package lkl;
public class MultipleReturns {
public static void f(int i){
System.out.println("Initialization that requires cleanup");
try{///在函数reutrn之前,finally回先运行
System.out.println("Point 1");
if(i==1) return ;
System.out.println("Point 2");
if(i==2) return ;
System.out.println("Point 3");
if(i==3) return ;
System.out.println("Point 4");
if(i==4) return;
}
///在上面的return之前,会先运行finally语句
finally{
System.out.println("Perferming cleanup");
}
}
public static void main(String[] args){
for(int i=1;i<=4;i++)
f(i);
}
}
那么总是会运行的finally语句用来干嘛?
在java中我们主要用来清理除内存外的其他一些资源,这些资源包含:已经打开的文件或网络连接,在屏幕上画的图像等.
五.异常处理与继承
最后一个问题是关于异常处理和继承的.当异常处理和继承一起发生的时候,我们须要关注的问题就是父类构造器抛出异常和子类构造器抛出异常的关系,在子类中重写父类方法是如何声明异常等问题.总结一下事实上也就是二个规则:
1).子类重写基类方法抛出的异常必须不比原基类方法抛出的异常类型更大.
2).尽管在语法上对子类构造器的异常声明没有规定,可是子类构造器的异常说明必须包含基类构造器的异常说明.(由于基类的构造器总是会被调用).
以下的代码对此进行了演示:
public class Foul extends BaseballException{
}
public class PopFoul extends Foul{
}
public class BaseballException extends Exception{
}
public class Strike extends BaseballException{
}
public class RainedOut extends StormException{
}
public interface Storm {
public void event() throws RainedOut;
public void rainHard() throws RainedOut;
}
public abstract class Inning {
public Inning() throws BaseballException{}
///定义抛出异常,可是实际上并没有
public void event() throws BaseballException{};
//声明抛出两个异常
public abstract void atBat() throws Strike ,Foul;
public void walk() {}
}
public class StormyInning extends Inning implements Storm{
public StormyInning() throws BaseballException,RainedOut{}
public StormyInning(String s)
throws Foul,BaseballException{}
//由于基类方法中没有抛出异常而子类覆盖后的方法抛出了
///所以编译报错
//void walk() throws PopFoul{}
//接口不能给基类方法加入异常类型
//public void event() throws RainedOut{}
///重写的方法抛出和基类方法一样的异常是能够的
public void rainHard() throws RainedOut{}
//基类的方法抛出了异常,而重写的方法不抛出也是能够的
public void event() {}
//重写的方法抛出基类异常的子类也是能够的
public void atBat() throws PopFoul{}
public static void main(String[] args) {
try{///以下的两句代码中构造器中可能抛出RainedOut,BaseballException异常
///atBat()方法可能抛出PopFoul异常.所以须要处理三种异常
StormyInning si = new StormyInning();
si.atBat();
}
catch(PopFoul e){
System.out.println("Pop Foul");
}
catch(RainedOut e){
System.out.println("Rained Out");
}
catch(BaseballException e){
System.out.println("Generic baseball");
}
try{//这段代码进行了向上转型,所以处理上面的三种情况外
///还必须处理基类atBat()方法抛出的Strike异常
Inning i = new StormyInning();
i.atBat();
}
catch(Strike e){
System.out.println("Strike");
}
catch(PopFoul e){
System.out.println("Pop Foul");
}
catch(RainedOut e){
System.out.println("Rained Out");
}
catch(BaseballException e){
System.out.println("Generic baseball");
}
}
}