Thinking in java-34 Exception异常处理

1. 关于异常Exception

引入异常处理的初衷:The basic philosophy of java is that ‘badly formed code will not be run!
在引入异常处理机制之前,我们一般是通过设置flag或者某些值的方式来判定某些组件是否有错的。
这里写图片描述
但是,许多年过去后,几乎所有程序猿都自命不凡,认为自己写的代码无可挑剔–“Yes, errors may happen to others, but not in my code.”
一个方法如果没有显式抛出异常即:没有使用throws在方法声明中,则该方法将不抛出异常(除了一些继承自 RuntimeException的异常之外。)
在代码编译时被强制要求检查和实现的叫做‘必检异常’–Checkded Exception.

2. 栈轨迹

printStackTrace()方法所打印的信息也可以直接通过getStackTrace()方法得到。
这个方法返回了一个数组,数组中的内容是栈轨迹元素,每一个元素代表了一个栈帧。
Element zero is the top of the stack, and is the last method invocation in the sequence (the point this Throwable was created and thrown). The last element of the array and the bottom of the stack is the first method invocation in the sequence.
元素0在该栈的栈顶,也是按顺序方法调用的最后一个方法 (Throwable 对象被创建和抛出的地方). 数组的最后一个元素且是栈底的元素–是该方法调用的第一个方法。

package com.fqy.blog;

public class WhoCalled {
    static void f() {
        // Generate an exception to fill in the stack trace
        try {
            throw new Exception();
        } catch (Exception e) {
            /*
             * StackTraceElement[] java.lang.Throwable.getStackTrace() Return an
             * array of stack trace element representing, stack trace pertaining
             * to this throwable object.
             */

            for (StackTraceElement ste : e.getStackTrace()) {
                System.out.println(ste.getMethodName());
                // System.out.println(ste.getClassName());
                // System.out.println(ste.getFileName());
                // System.out.println(ste.getLineNumber());
            }
        }
    }

    // Method g() invoke f()
    static void g() {
        f();
    }

    // Method h() invoke g()
    static void h() {
        g();
    }

    public static void main(String[] args) {
        f();
        System.out.println("---------------------");
        g();
        System.out.println("---------------------");
        h();
    }
}

//Running result:
f
main
---------------------
f
g
main
---------------------
f
g
h
main

3. Re-throw an exception 重新抛出异常

这里有关于该问题的详细Q&A。
Q:为何要重新抛出异常,这有什么意义?
A:重新抛出异常在我们想要记下异常,但并不想处理异常时很有用。可以通过抛出新异常、直接throw; 、直接抛出原异常ex这几种方式实现。

try
{
    ... // some code
}
catch (Exception ex)
{
    ... // Do something
    throw new CustomException(ex);

    // or
    // throw;

    // or
    // throw ex;
}
package com.fqy.blog;

public class ThrowExceptionDemo {

    public static void main(String[] args) {
        f2();
        System.out.println("main");
    }

    static void f0() throws Exception {
        throw new Exception();
    }

    static void f1() throws Exception {
        try {
            f0();
        } catch (Exception e) {
            e.printStackTrace();

            /*
             * 调用fillStackTrace()会清空原有栈信息,并在当前位置重新建立新栈信息。 效果就像是从该位置抛出一样。
             */
            e.fillInStackTrace();
            throw e;
        }
        // If e.fillInStackTrace() invoked here, the code below will never
        // output.
        System.out.println("f1()");
    }

    static void f2() {
        try {
            f1();
        } catch (Exception e) {
            System.out.println("Never reached here.\n");
            e.printStackTrace();
        }
        System.out.println("f2()");
    }
}
//Running result:
java.lang.Exception
    at com.fqy.blog.ThrowExceptionDemo.f0(ThrowExceptionDemo.java:11)
    at com.fqy.blog.ThrowExceptionDemo.f1(ThrowExceptionDemo.java:16)
    at com.fqy.blog.ThrowExceptionDemo.f2(ThrowExceptionDemo.java:33)
    at com.fqy.blog.ThrowExceptionDemo.main(ThrowExceptionDemo.java:6)
java.lang.Exception
    at com.fqy.blog.ThrowExceptionDemo.f1(ThrowExceptionDemo.java:23)
    at com.fqy.blog.ThrowExceptionDemo.f2(ThrowExceptionDemo.java:33)
    at com.fqy.blog.ThrowExceptionDemo.main(ThrowExceptionDemo.java:6)
f2()
main

在处理一个异常时也可以在处理异常时抛出一个新的异常。如果我们这样做–即在处理原始异常时抛出新的异常的效果和在处理异常时使用e.fillInStackTrace() 一样的效果:异常信息产生源头的信息丢失了,所能得到的信息只是从新的抛出位置开始的。一个简单的demo如下所示:

package com.fqy.blog;

class OneException extends Exception {
    public OneException(String str) {
        super(str);
    }
}

class TwoException extends Exception {
    public TwoException(String str) {
        super(str);
    }
}

public class ThrowNewExceptionDemo {
    // A methods that throws a kind of exception.
    static void f() throws OneException {
        System.out.println("Original exception in f()");
        throw new OneException("thrown from f()");
    }

    public static void main(String[] args) {
        try {
            try {
                f();
            } catch (OneException e) {
                System.out.println("Caught in inner try");
                e.printStackTrace();
                throw new TwoException("Thrown from inner try");

            }
        } catch (TwoException e) {
            System.out.println("Caught in outer try.");
            e.printStackTrace();
        }
    }
}
//Running result:
Original exception in f()
Caught in inner try
com.fqy.blog.OneException: thrown from f()
Caught in outer try.
    at com.fqy.blog.ThrowNewExceptionDemo.f(ThrowNewExceptionDemo.java:19)
    at com.fqy.blog.ThrowNewExceptionDemo.main(ThrowNewExceptionDemo.java:25)
com.fqy.blog.TwoException: Thrown from inner try
    at com.fqy.blog.ThrowNewExceptionDemo.main(ThrowNewExceptionDemo.java:29)

4. Java异常链机制

这里有详细Q&A。
Q:为何有异常链的存在?
A:异常链的存在允许我们把不同的异常关联起来,即:一个异常可以用来描述另一个异常产生的原因。比如一个算法抛出了ArithmeticException, 而实际的异常原因是I/O错误,那该算法只能抛出ArithmeticException给调用者,调用者无从知晓真正产生问题的原因。

package com.fqy.blog;

import java.io.IOException;

public class ExceptionChainDemo {
    static void divide(int op1, int op2) {
        if (op2 == 0) {
            ArithmeticException ae = new ArithmeticException("Top layer");
            ae.initCause(new IOException("The real cause"));
            throw ae;
        } else
            System.out.println(op1 / op2);
    }

    public static void main(String[] args) {
        try {
            divide(10, 0);
        } catch (ArithmeticException ae) {
            System.out.println("Caught " + ae);
            System.out.println("Actual cause " + ae.getCause());
        }
    }

}
//Running result:
Caught java.lang.ArithmeticException: Top layer
Actual cause java.io.IOException: The real cause

另一个例子:

package com.fqy.blog;

class ExceptionA extends Exception {
    public ExceptionA(String str) {
        super(str);
    }
}

class ExceptionB extends ExceptionA {
    public ExceptionB(String str) {
        super(str);
    }
}

class ExceptionC extends ExceptionB {
    public ExceptionC(String str) {
        super(str);
    }
}

public class NeverCaught {
    static void f() throws ExceptionB {
        throw new ExceptionB("Exception B");
    }

    static void g() throws ExceptionC {
        try {
            f();
        } catch (ExceptionB e) {
            ExceptionC c = new ExceptionC("Exception C");
            // initialize the cause of the throwable to the specified value.
            c.initCause(e);
            throw c;
        }
    }

    public static void main(String[] args) {
        try {
            g();
        } catch (ExceptionC e) {
            System.out.println("Caught: " + e);
            // return the cause of this throwable or none if the cause if
            // nonexistent or unknown.
            System.out.println("Actual caught is: " + e.getCause());
            // e.printStackTrace();
        }
    }

}
//Running result:
Caught: com.fqy.blog.ExceptionC: Exception C
Actual caught is: com.fqy.blog.ExceptionB: Exception B

5. RuntimeException

运行时异常是一种特例,因为编译器并不强制要求对于这些类型的转换。
只有RuntimeException(and subclasses) 不被编译器unchecked强制转换;因为编译器会强制处理所有必需转换checked的异常。
Q:为何编译器不对这一类数据进行转换呢?
A:因为RuntimeError代表了一类运行错误。

  1. 这是一类我们不能预见的错误。比如一些我们无法控制的空引用。
  2. 这是一类我们应该在代码中预先考虑好的问题(比如说数组下标越界错误等)。

6. finally & lost exception

  • finally 子句即使在return语句之后,其依然会被执行。
package thinking.exception;

import static fqy.iss.utils.Print.print;

public class MultipleReturns
{
    public static void f(int i)
    {
        print("Initialization that requires cleanup");
        try
        {
            print("Point 1");
            if (i == 1)
                return;
            print("Point 2");
            if (i == 2)
                return;
            print("Point 3");
            if (i == 3)
                return;
            print("End");
            return;
        }
        finally
        {
            print("Performing cleanup");
        }
    }

    public static void main(String[] args)
    {
        for (int i = 1; i <= 4; i++)
        {
            print("****************************");
            f(i);
        }
    }

}
//Running result:
****************************
Initialization that requires cleanup
Point 1
Performing cleanup
****************************
Initialization that requires cleanup
Point 1
Point 2
Performing cleanup
****************************
Initialization that requires cleanup
Point 1
Point 2
Point 3
Performing cleanup
****************************
Initialization that requires cleanup
Point 1
Point 2
Point 3
End
Performing cleanup
  • finally 子句可能会使得try代码块中抛出的异常被沉默。
package thinking.exception;

public class ExceptionSilencer
{

    public static void main(String[] args)
    {
        try
        {
            throw new RuntimeException();
        }
        finally
        {
            // Using 'return' inside the finally block will silence any thrown
            // exception.
            return;
        }
    }

}
//Running result:无输出
~~

7. 子类重写的方法不能抛出比父类更加宽泛的异常,或抛出新异常

这里有关于该问题的详细Q&A。
解答是:因为多态机制。
如果在B内的foo()方法中抛出了新的异常,则编译器并不能强制捕获它,这和多态初衷相悖离。

class A{
    public void foo() throws IOException{
    }
}
class B extends A{
    @Override
    public void foo() throws SocketException{ //Ok
    }
    @Override
    public void foo() throws SQLException{ //Not allowed
    }
}

    A a = new B();
    try{
        a.foo();
    }catch(IOException ex){
    //Forced to catch this by the compiler
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值