Java异常简介及异常信息缺失处理

java异常简介

Error

Error用来表示编译时和系统错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如:系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢出等。如java.lang.StackOverFlowError和Java.lang.OutOfMemoryError。对于这类错误,Java编译器不去检查他们。对于这类错误的导致的应用程序中断,仅靠程序本身无法恢复和预防,遇到这样的错误,建议让程序终止。

Exception

Exception(异常)是应用程序中可能的可预测、可恢复问题。异常一般是在特定环境下产生的,通常出现在代码的特定方法和操作中。遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常。
Exception又分为运行时异常(Runtime Exception)和受检查的异常(Checked Exception )。 RuntimeException:Java编译器不去检查它,也就是说,当程序中可能出现这类异常时,即使没有用try……catch捕获,也没有用throws抛出,还是会编译通过,如除数为零的ArithmeticException、错误的类型转换、数组越界访问和试图访问空指针等。
Checked Exception:这类异常要么try...catch捕获处理,要么用throws字句声明抛出,交给它的调用方处理,否则编译不会通过。

try-catch-finally-return执行顺序:

1、不管是否有异常产生,finally块中代码都会执行,即便在try或catch中加入了continue、break或者return。
2、finally是在return后面的表达式运算后执行的,所以函数返回值是在finally执行前确定的。无论finally中的代码怎么样,返回的值都不会改变,仍然是之前return语句中保存的值;
例如:

    public static void main(String[] args) {
        System.out.println(Main.test());;
    }
    public static int test() {
        int x = 1;
        try {
            x += 2;
            return x;
        } finally {
            ++x;
        }
    }
复制代码

程序返回值:3

3、finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。

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

    public static int test() {
        int x = 1;
        try {
            x += 2;
            return x;
        } finally {
            ++x;
            return x;
        }
    }
复制代码

程序返回值:4

异常信息缺失情况

重新抛出异常

有时我们在捕获到异常后,可能在捕获的地方不适合处理该异常,我们需要将它重新抛出:

    catch(Exception e){
        throw e; 
    } 
复制代码

这样我们可以将异常交给上一级环境处理,但是这样抛出的异常携带的信息,也就是printStackTrace()方法显示的是原来异常抛出点的调用栈信息,而非重新抛出点的信息,这样重新抛出点的调用信息就被掩盖了。如果想更新重新抛出点信息到这个异常调用栈中,可以使用fillInStackTrace()方法,那么当前调用栈的信息就更新到了这个异常对象中了:

    catch(Exception e){
        throw e.fillInStackTrace(); 
    }
复制代码

还有一种情况,也会存在类似的丢失现象:

catch(Exception e){
    throw new Exception();
} 
复制代码

这样我们上一级的抛出的异常信息就丢了,接收异常的地方就是只能得到new Exception()这个异常的信息。在JDK1.4以前如果你希望保存丢失的那个异常信息,只能通过编码的方式自己实现,而在JDK1.4后,Throwable类添加了一个Throwable类型的属性cause,用来表示原始异常,那么我们就可以通过异常链从新的异常追踪到异常最初发生的位置。我们可以通过构造函数或initCause(Throwable cause)方法传入一个Throwable对象用来记录原始异常。

Throwable.java:

    /**
     * The throwable that caused this throwable to get thrown, or null if this
     * throwable was not caused by another throwable, or if the causative
     * throwable is unknown.  If this field is equal to this throwable itself,
     * it indicates that the cause of this throwable has not yet been
     * initialized.
     *
     * @serial
     * @since 1.4
     */
    private Throwable cause = this;
复制代码

异常链

另外,finally语句也可能会造成异常信息丢失:

class SomeException extends Exception {
    @Override
    public String toString() {
        return "Some exception";
    }
}

class OtherException extends Exception {
    @Override
    public String toString() {
        return "Other exception";
    }
}

public class Main {
    void some() throws SomeException {
        throw new SomeException();
    }
    void other() throws OtherException {
        throw new OtherException();
    }
    public static void main(String[] args) {
        try {
            Main test = new Main();
            try {
                test.some();
            } finally {
                test.other();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
复制代码

程序返回值

Other exception
	at Main.other(Main.java:23)
	at Main.main(Main.java:32)
复制代码

把最外一层try看作是上一级程序的处理,在这个try里面发生了两次异常,但是我们只能获得从finally中抛出的异常信息,而在some()方法中的异常信息丢失,这种情况我们称上一个异常被抑制了。即finally中抛出的异常会抑制其对应的try或catch中抛出的异常。
在JDK1.7之后,Throwable添加了一个属性suppressedExceptions,用来表示被抑制的异常。我们可以使用addSuppressed(Throwable exception)和getSuppressed()方法解决此问题

    public static void main(String[] args) {
        try {
            Main test = new Main();
            Exception exception = null;
            try {
                test.some();
            } catch (SomeException e) {
                exception = e;
                throw e;
            } finally {
                if (exception != null) {
                    try {
                        test.other();
                    } catch (OtherException e) {
                        exception.addSuppressed(e);
                    }
                } else {
                    test.other();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
复制代码

程序返回值

Some exception
	at Main.some(Main.java:19)
	at Main.main(Main.java:31)
	Suppressed: Other exception
		at Main.other(Main.java:23)
		at Main.main(Main.java:38)
复制代码

栈轨迹

捕获到异常时,往往需要进行一些处理。比较简单直接的方式就是打印异常栈轨迹Stack Trace。
通过查看源码Throwable.java中printStackTrace()方法,我们可以对上述异常信息丢失的解决办法有更加清晰地认识:

    private void printStackTrace(PrintStreamOrWriter s) {
        // Guard against malicious overrides of Throwable.equals by
        // using a Set with identity equality semantics.
        Set<Throwable> dejaVu =
            Collections.newSetFromMap(new IdentityHashMap<Throwable, Boolean>());
        dejaVu.add(this);

        synchronized (s.lock()) {
            // Print our stack trace
            s.println(this);
            StackTraceElement[] trace = getOurStackTrace();
            for (StackTraceElement traceElement : trace)
                s.println("\tat " + traceElement);

            // Print suppressed exceptions, if any
            for (Throwable se : getSuppressed())
                se.printEnclosedStackTrace(s, trace, SUPPRESSED_CAPTION, "\t", dejaVu);

            // Print cause, if any
            Throwable ourCause = getCause();
            if (ourCause != null)
                ourCause.printEnclosedStackTrace(s, trace, CAUSE_CAPTION, "", dejaVu);
        }
    }
复制代码

从源码中我们可以发现,在打印异常栈轨迹时,打印顺序为:异常本身栈轨迹信息、被抑制异常栈轨迹信息、原始异常栈轨迹信息。

try-with-resources 语法糖

在JDK 7之前,资源需要我们手动关闭。如:

    String s = "Some String";
    BufferedWriter writer = null;
        try {
        writer = new BufferedWriter(new FileWriter("test"));
        writer.write(s, 0, s.length());
    } catch (IOException x) {
        System.err.format("IOException: %s%n", x);
    } finally {
        if (writer != null) {
            try {
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
复制代码

try-with-resources 是 JDK 7 中一个新的异常处理机制,它能够很容易地关闭在 try-catch 语句块中使用的资源。所谓的资源(resource)是指在程序完成后,必须关闭的对象。try-with-resources 语句确保了每个资源在语句结束时关闭。所有实现了 java.lang.AutoCloseable 接口(其中,它包括实现了 java.io.Closeable 的所有对象),可以使用作为资源。

在 try 语句中越是最后使用的资源,越是最早被关闭。

class Resource implements AutoCloseable {
    void doSome() {
        System.out.println("do something");
    }
    @Override
    public void close() throws Exception {
        System.out.println("resource is closed");
    }
}
public class Main {
    public static void main(String[] args) {
        try(Resource res = new Resource()) {
            res.doSome();
        } catch(Exception ex) {
            ex.printStackTrace();
        }
    }
}
复制代码

程序返回值

do something
resource is closed
复制代码

try-with-resources 声明在 JDK 9 已得到改进。如果你已经有一个资源是 final 或等效于 final 变量,您可以在 try-with-resources 语句中使用该变量,而无需在 try-with-resources 语句中声明一个新变量。

try-with-resources 是语法糖,那么背后又是如何实现的呢?
下面代码:

    try(FileInputStream fstream = new FileInputStream("test")) {
        fstream.read();
    } catch (IOException e) {
        e.printStackTrace();
    }
复制代码

编译后反编译为:

    try {
        FileInputStream fstream = new FileInputStream("test");
        Throwable var2 = null;

        try {
            fstream.read();
        } catch (Throwable var12) {
            var2 = var12;
            throw var12;
        } finally {
            if (fstream != null) {
                if (var2 != null) {
                    try {
                        fstream.close();
                    } catch (Throwable var11) {
                        var2.addSuppressed(var11);
                    }
                } else {
                    fstream.close();
                }
            }
        }
    } catch (IOException var14) {
        var14.printStackTrace();
    }
复制代码

从反编译代码可以发现,其背后也是使用了 addSuppressed(Throwable exception) 方法来实现对异常信息的处理

参考资料:

  1. Java 异常详解
  2. 在 JDK 9 中更简洁使用 try-with-resources 语句
  3. 为什么try..finally块不注册原始异常抑制?
  4. 有return的情况下try catch finally的执行顺序
  5. 谈一谈Java中的Error和Exception

转载于:https://juejin.im/post/5bf2940be51d457c042c313c

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值