三个JAVA臭皮匠程序员

Java异常机制

2018-06-01  本文已影响113人  后厂村老司机

前言:

写了两周的博客了,深的浅的都有,总有人问我写博客有什么用?能出书吗?写的太浅有人看吗?对找工作有什么帮助吗?不想争辩,也不愿把自己说的多么高大上,分享本身是一件简单的小事,但是能给我带来快乐,我享受别人看过我博客之后有种觉得有收获那种感觉,就这么简单。还有那些害怕写博客被别人质疑太浅的,其实大可不必,总有一些人掌握的知识没有你多,也许你的一句话就解决了他们的疑问。言归正传,今天给大家带来Java异常体系。

1、Java异常体系结构图

Java异常体系图
1.1、结构角度分类

Error类:系统错误,我们最熟悉的OutOfMemeroyError、StackOverFlowError、NoClassDefoundError等都是该类的子孙,这些错误主要与机器有关。值得注意的是Error我们也可以通过try catch捕获到,尽管捕获到意义不大,比如OutOfMemeroyError我们捕获到要进行处理是不可能的,因为系统已经不工作了。
Exception:异常类,一个Exception发生了那么证明程序员写的代码出问题了,而不是机器的问题。

1.2、Java对异常处理要求分类

Checked Exception类:
A:体系图中的红色部分,编译期间Java编译器会强制要求程序员对该类异常进行处理,要么try catch、要么throw抛出去给调用层处理,否则无法执行。Eclipse里面那些给我们提示的实际上都是这类异常。
B:为什么会存在这种异常呢?简单来说就是对客观上存在的潜在错误进行预处理,就像我们可能并不是每个人都会得天花,但是我们必须接种天花疫苗。Java号称平台无关,程序员写的代码可能会出现在各种环境中,拿IOException来说,假如代码运行的机器断网了,那么网络IO就有可能出现异常,如果不处理那么我们的程序就崩了,可检查异常一般都是大概率出现的异常,因此需要提前处理。

Unchecked Exception类:
A:体系图中蓝色部分,主要包括Error和RuntimeException,编译器不会检查该类型异常,也检查不到,比如我们最讨厌的NullPointerException,编译器不会强制程序员处理这种异常。当然,程序员可以主动try catch捕获这种异常然后进行处理,但是假如你写了除0的代码,与其花时间和代码量去做捕获处理,还不如从根本上杜绝这个问题,RuntimeException很大程度上能反应一个程序员代码的健壮性。
B:既然无法捕获,这种异常有什么存在的必要呢?这就不得不说异常的另一个重要属性,那就是传递信息的属性,一个异常本身就是一种信息,它会告诉程序员哪一行代码抛出了异常,调用链是什么样子的,异常是什么原因导致的,常见的异常如下。

//这一行冒号左边标注了异常类型,冒号右边标注了异常原因
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 1
        //这一行是异常抛出位置,在代码的第15行,c方法里
    at com.huo.demos.test.Test.c(Test.java:15)
        //以下三行是方法异常传递路径,即方法调用路径
    at com.huo.demos.test.Test.b(Test.java:10)
    at com.huo.demos.test.Test.a(Test.java:6)
    at com.huo.demos.test.Test.main(Test.java:19)

2、自定义异常

2.1、什么时候需要自定义异常?
2.2、如何自定义异常?
public class EException extends Exception {
    public EException() {
        super();
    }
    public EException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
    public EException(String message, Throwable cause) {
        super(message, cause);
    }
    public EException(String message) {
        super(message);
    }
}
public class RException extends RuntimeException {
    public RException() {
        super();
    }
    public RException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
    public RException(String message, Throwable cause) {
        super(message, cause);
    }
    public RException(String message) {
        super(message);
    }
    public RException(Throwable cause) {
        super(cause);
    }
}

然后:继承Exception的异常应该出现在这种情况里,即我的代码极有可能出现这种异常,因为我无法考虑到所有环境,因此我抛出给调用方根据自己的环境进行对应处理。继承RuntimeException的异常应该出现在此种环境里,即我的代码出现这种异常的可能性不是特别大,但是还是有出现的可能,而在出现这种异常的时候我能够给调用方以足够的提示信息告知他发生了什么。

3、异常捕获机制

3.1、catch捕获范围

从第一个catch开始到最后一个catch形成捕获队列,按照队列的顺序谁先捕获到就归谁处理。这个过程要考虑多态的问题,由于Exception是许多异常类的父类,因而如果处在队列前面,后面的队列的声明就没有意义了。

public static int bMethod() {
        try {
            return cMethod(true);
        } catch (EException e) {   //第一个捕获
            e.printStackTrace();
        } catch (Exception e) {  //第一个没捕获到,第二个来捕获
            e.printStackTrace();
        } finally {
            return 1;
        }
    }
3.2、finally的执行问题

finally的中文意思是最终,也就是段try catch finally代码的try catch该执行的都执行完之后最终要执行finally里的代码。简单起见可以把try catch finally看成一个代码块。
为了更好的解释finally运行机制,我们先了解一下虚拟机栈帧。

虚拟机栈帧
我们知道,每个线程都有一个自己的栈空间,实际上,当方法执行的时候JVM会把方法制作成栈帧压入到操作栈中,每个方法都对应着一个栈帧,当前执行的栈帧总是位于栈顶。
栈帧包含局部变量表、操作数栈、动态连接、方法返回地址。这里的方法返回地址实际上是在方法遇到ret指令之后返回值存放的位置的地址,也就是说返回值并没有直接返回给方法的调用者,而是中间又多了一步将返回值存放到栈的某一个位置。在真正把返回值交给上层调用者之前,如果遇到break、continue、return、抛出异常等情况JVM会清除栈中的返回值
public static int method1() {
        int k = 0;
        try {
            k = 1;
            //返回的k=1首先存储到栈中的返回值位置,如果顺利完成就把该位置的值返回,如果遇到特殊指令如break、continue、return、抛异常就清除
            return k;                         
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //清除原来的返回值,把100放到返回值的位置上,如果顺利结束就返回,否则清除
            return 100;                  
        }
    }
public static int method2() {
        int k = 1;
        while (true) {
            try {
                return k;
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                //遇到break,返回值位置的值被清除
                break;
            }
        }
        k=100;
        return k;
    }
public static int method1() {
        int k = 1;
        //此处抛异常或者try和catch里调用system.exit()方法finally就不会执行。
        Integer.parseInt(null);
        try {
            return k;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            return k;
        }
    }

总结

其实日常开发我们遇到最多的就是NPE问题,NPE问题即是小问题又是破坏健壮性的大问题,我们应该更好的防范一下。我刷leetcode和进行一些日常开发总结了一些经验,供大家参考下:

上一篇 下一篇

猜你喜欢

热点阅读