第十二章:通过异常处理问题

通过异常处理问题

  • 异常机制使代码的阅读、编写和调试工作更加井井有条。

基本异常

  • 异常情形是指阻止当前方法或作用域继续执行的问题。普通问题是指在当前环境下能够得到足够的信息,总能处理这个错误(比如说if else)。而异常情形是指在当前环境下无法获得必要的信息来解决问题。此时你能做的就是从当前环境跳出,并且把问题提交给以上一级环境。这就是所谓的抛出异常
  • 当抛出异常后,有几件事情随之发生。首先Java会在堆上创建异常对象。然后,当前的执行路径被终止,并且从当前环境中弹出对异常对象的引用。此时异常处理机制接管程序,并开始寻找一个恰当的地方来继续执行程序。这个恰当的地方就是异常处理程序,它的任务是将程序从错误状态中恢复,以使程序要么换一种方式运行,要么继续运行下去。
if (obj == null) {
    //抛出一个异常给另外一个环境(下面会介绍),这个环境就不再继续执行了。
    throw new NullPointerException("obj is null");//一个继承Throwable类的对象
}

创建自定义异常

  • 下面是一个创建自定义异常的例子:
public class Test {
    public static void main(String args[]) {
        Test t = new Test();
        try {
            t.f();
        } catch (MyException e) {
            //打印从“方法调用处直到异常抛出处”的方法调用序列
            e.printStackTrace();
        }
    }
    //此处跟在方法声明后面throws语句为“异常说明”,如果有多个用逗号隔开
    public void f() throws MyException {
        throw new MyException("hello world!");
    }
}
class MyException extends Exception {
    public MyException() {}
    public MyException(String s) {super(s);}
}
  • 另外我们可以记录异常
import java.util.logging.*;
public class Test {
    private static Logger logger = Logger.getLogger("Test");
    public static void main(String args[]) {
        Test t = new Test();
        try {
            t.f();
        } catch (MyException e) {
            //可以提取下面三句代码为一个方法,只要就可以任意调用了。
            StringWriter sw = new StringWriter();
            e.printStackTrace(new PrintWriter(sw));
            logger.severe(sw.toString());
        }
    }
    public void f() throws MyException {
        throw new MyException("hello world!");
    }
}

捕获所有异常

  • 可以只写一个异常处理程序来捕获所有类型的异常。通过捕获异常类型的基类Exception,就可以做到这一点。但是如果要捕获多种异常时,需要将基类异常放置在最后面。

栈轨迹

  • 直接看以下代码:
public class Test {
    public static void main(String args[]) {
        Test t = new Test();
        t.f();
        System.out.println("--------------------");
        t.g();
        System.out.println("--------------------");
        t.h();
    }
    public void f() {
        try {
            throw new Exception();
        } catch (Exception e) {
            //e.getStackTrace()能获得一个StackTraceElement数组
            for (StackTraceElement ste : e.getStackTrace()) {
                //System.out.println(ste.getFileName());
                //System.out.println(ste.getClassName());
                System.out.println(ste.getMethodName());
                //System.out.println(ste.getLineNumber());
            }
        }
    }
    public void g() {f();}
    public void h() {g();}
}
------------------------------执行结果如下:
f
main
--------------------
f
g
main
--------------------
f
g
h
main

重新抛出异常

  • 我们可以在catch块中重新抛出异常,如果不做任何处理的话,该异常依旧保留了之前的信息。但是如果我们使用了fillInStackTrace()方法,那么异常信息就会被更新。
public class Test {
    public static void main(String args[]) {
        Test t = new Test();
        try {
            t.g();
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            t.h();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public void f() throws Exception {
        throw new Exception();
    }
    public void g() throws Exception {
        try {
            f();
        } catch (Exception e) {
            throw e;
        }
    }
    public void h() throws Exception {
        try {
            f();
        } catch (Exception e) {
            //更新异常信息
            throw (Exception) e.fillInStackTrace();
        }
    }
}
-------------------执行结果
java.lang.Exception
    at test.Test.f(Test.java:18)
    at test.Test.g(Test.java:22)
    at test.Test.main(Test.java:7)
java.lang.Exception
    at test.Test.h(Test.java:31)
    at test.Test.main(Test.java:12)
  • 还有一部分是关于异常链的用法,我是看不下去了,有兴趣的可以看看。

Java标准异常

  • 在简单了解Java的异常机制后,我们是否会思考,为什么平常没有为一些代码设置抛出异常,同样也会抛出异常呢?(比如ClassCastExceptionNullPointerException

RuntimeException

  • 运行时异常是由Java虚拟机自动抛出的异常。其实我们平常一直都享受着运行时异常的好处。不然的话每使用一个引用都抛出或捕捉一个NullPointerException恐怕会累死你。
//可以不用加上异常说明即可通过编译
public void f() {
    throw new NullPointerException();
}
  • 属于运行时异常的类型有很多,由于会被Java虚拟机自动抛出,所以也就不用在异常说明中将他们列出来。这些异常都是从RuntimeException类继承而来。它们也被称为不受检查异常。这种异常属于错误,将被自动捕获,就不用亲自动手了。
public class Test {
    public static void main(String args[]) {
        Test t = new Test();
        t.g();
        System.out.println("正常结束");
    }
    public void f() {
        throw new RuntimeException("异常结束");
    }
    public void g() {
        f();
    }
}
------------ 运行结果:
Exception in thread "main" java.lang.RuntimeException: 异常结束
    at test.Test.f(Test.java:10)
    at test.Test.g(Test.java:13)
    at test.Test.main(Test.java:6)
  • 如果不捕获该类型异常,则该异常会一直向上抛出。对于这种异常类型,编译器不需要异常说明,其输出被报告给了System.err。即如果RuntimeException没有被捕获而直达main(),那么程序在退出前将调用异常的printStackTrace()方法。
  • 小结:RuntimeException之所以不需要捕获,究其原因,其代表的是编程错误:
    1. 无法预料的错误。比如从你控制范围之外传递进来的null引用。
    2. 作为程序员,应该在代码中进行检查的错误。(比如对于ArrayIndexOutOfBoundsException,就得注意一下数组的大小了。这种异常应该在编辑代码时规避,而不是运行时捕获。)在一个地方发生的异常,常常会在另一个地方导致错误。
  • 另外合理利用异常信息进行代码调试也是一项很重要的工作技巧。

使用finally进行清理

  • 对于一些代码,可能会希望无论try块中的异常是否抛出,它们都能得到执行。这通常适用于内存回收之外的情况。此时我们并需要finally块。普通例子不再列举了,下面看一些特殊情况的例子:
public class Test {
    public static void main(String args[]) {
        Test t = new Test();
        t.test1();
        System.out.println(t.test2());
    }
    void test1() {
        try {
            System.out.println("no exception");
            return;
        } finally {
            System.out.println("finally");
        }
    }
    String test2() {
        String s = null;
        try {
            s = "2";
            return s;
        } finally {
            s = "1";
            //return s;
        }
    }
}
----------------猜一猜运行结果吧
  • 如果在try之中return了,return s这个操作会先入栈,这时s是”2”。在执行完finally中的代码后,s虽然被改变成了”1”。但是不会影响之前的return操作。差不多就是这个意思,可能描述有点错误。所以不必担心finally的操作会影响try中的retrun值。不过在return之前是会先去执行finally中的代码的。不过你要是在finally中return了,那就另当别论了。

小结

  • 因为平常用法比较简单,所以我省略了很多小节。以下是关于如何使用异常的总结:
    1. 在恰当的级别处理问题。(在知道该如何处理的情况下才捕获异常。)
    2. 解决问题并且重新调用产生异常的方法。
    3. 进行少许修补,然后绕过异常发生的地方继续执行。
    4. 用别的数据进行计算,以代替方法预计返回的值。
    5. 把当前运行环境下能做的事情尽量做完,然后把相同的异常重抛到更高层。
    6. 把当前运行环境下能做的事情尽量做完,然后把不同的异常重抛到更高层。
    7. 终止程序。
    8. 进行简化。(如果你的异常模式使问题变得太复杂,那用起来会非常痛苦也很烦人。)
    9. 让类库和程序更安全。(这既是在为调试做短期投资,也是在为程序的健壮性做长期投资。)
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值