美文网首页程序员
细节!关于java异常的总结,我还没见过比这更详细的

细节!关于java异常的总结,我还没见过比这更详细的

作者: 程序员伟杰 | 来源:发表于2020-08-06 14:06 被阅读0次

    前言

    异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。

    异常的体系结构:

    Thorwable类是所有异常和错误的超类,有两个子类Error和Exception,分别表示错误和异常。其中异常类Exception又分为运行时异常(RuntimeException)和非运行时异常,这两种异常有很大的区别,也称之为不检查异常(Unchecked Exception)和检查异常(Checked Exception)。

    运行时异常和非运行时异常:

    运行时异常都是RuntimeException类及其子类异常,如NullPointerException、IndexOutOfBoundsException等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。

    非运行时异常是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、ClassNotFoundException等以及用户自定义的异常。

    自定义异常:

    ​ 程序中有时会出现特有的异常,而这些异常并未被java所描述并封装对象,对于这些特有的异常可以按照java的封装思想。将特有的异常按照Java异常机制进行自定义的异常封装。

    ​ 异常体系具备一个特有的特性:可抛性:可以被throw关键字操作。自定义异常被抛出,必须是继承Throwable,或者继承Throwable的子类,该对象才可以被throw抛出。

    一般自定义异常继承Exception或者RuntimeException,选择哪种继承取决于是否需要对异常进行捕获处理。

    在异常中往往需要封装异常信息,查阅异常源码,发现自己父类构造函数中有关于异常信息的操作,那么在自己定义的异常中需要将这些信息传递给父类,让父类帮我们进行封装即可。

    class NoAgeException extends RuntimeException{
        /*
        为什么要定义构造函数,因为看到Java中的异常描述类中有提供对问题对象的初始化方法。
        */
        NoAgeException(){
            super();
        }
        NoAgeException(String message)  {
            super(message);// 如果自定义异常需要异常信息,可以通过调用父类的带有字符串参数的构造函数即可。
        }
    }
    
    

    异常处理:

    异常处理的两大组成要素:抛出异常和捕获异常。这两大要素共同实现程序控制流的非正常转移。

    抛出异常分为:显式和隐式两种。

    显式抛异常的主题是应用程序,它指的是在程序中使用 “throw” 关键字。手动将异常实例抛出。

    隐式抛异常的主题是java虚拟机,它指的是java虚拟机在执行过程中,碰到无法继续执行的异常状态,自动过抛出异常。举例来说,java虚拟机在执行读取数组操作时,发现输入的索引值是负数,故而抛出数组索引越界异常(ArrayIndexOutOfBoundsException)。

    捕获异常则涉及了如下三种代码块:

    1、try代码块:用来标记需要进行异常监控的代码。

    2、catch代码块:跟在try代码块之后,用来捕获在try代码块中触发的某种类型的异常。除了声明所捕获异常的类型之外,catch代码块还定义了针对该异常类型的异常处理器。在java中try代码块后可以跟多个catch代码块,来捕获不同的异常。java虚拟机会从上至下匹配异常处理器。因此,前面的catch代码块所捕获的异常类型不能覆盖后面的,否则编译器会报错。

    3、finally代码块:跟在try代码块和catch代码块之后,用来声明一段必定运行的代码。它的设计初衷是为了避免跳过某些关键的清理代码。例如关闭已打开的系统资源。

    在程序正常执行的情况下,这段代码会在try代码块执行之后执行。否则,也就是在try代码块抛异常的情况下,如果该异常没有被捕获,finally代码块会直接运行,并且在运行之后重新抛出异常。

    如果该异常被catch代码块捕获,finally代码块则在catch代码块之后运行。在某些不幸的情况下,catch代码块也触发了异常,那么finally代码块同样会执行,并会抛出catch代码块触发的异常。在某极端不幸的情况下,finally代码块也触发了异常,那么只好中断当前finally代码块的执行,并往外抛出异常。

    非运行时异常:

    捕获格式

    try
    {
        //需要被检测的语句。
    }
    catch(异常类 变量)//参数。
    {
        //异常的处理语句。
    }
    
    finally
    {
        //一定会被执行的语句。
    }
    
    
    class Demo{
        /*
        如果定义功能时有问题发生需要报告给调用者。可以通过在函数上使用throws关键字进行声明。
        */
        void show(int x)throws Exception    {
            if(x>0){
                throw new Exception();
            }else{
                System.out.println("show run");
            }
        }
    }
    class ExceptionDemo{
        public static void main(String[] args)//throws Exception//在调用者上继续声明。 
        {
            Demo d = new Demo();
            try {
                d.show(1);//当调用了声明异常的方法时,必须有处理方式。要么捕获,要么声明。
            }
            catch (Exception ex)//括号中需要定义什么呢?对方抛出的是什么问题,在括号中就定义什么问题的引用。
            {
                System.out.println("异常发生了");
            }
            System.out.println("Hello World!");
        }
    }
    
    

    运行时异常:

    描述长方形。

    • 属性:长和宽。
    • 行为:获取面积。

    考虑健壮性问题。万一长和宽的数值非法。描述问题,将问题封装成对象,用异常的方式来表示。

    class NoValueException extends RuntimeException{
        NoValueException()  {
            super();
        }
        NoValueException(String message)    {
            super(message);
        }
    }
    
    class Rec{
        private int length;
        private int width;
        Rec(int length,int width)   {
            if(length<=0 ||width<=0)    {
                //抛出异常,但是不用声明,不需要调用者处理。就需要一旦问题发生让调用者端停止,让其修改代码。
                throw new NoValueException("长或者宽的数值非法");
            }
            this.length = length;
            this.width = width;
        }
        /**
        定义面积函数。
        */
        public int getArea()    {
            return length*width;
        }
    }
    class  ExceptionTest{
        public static void main(String[] args) {
            Rec r = new Rec(-3,4);
            int area = r.getArea();
            System.out.println("area="+area);
        }
    }
    
    

    JVM的捕获异常机制

    在编译生成的字节码中,每个方法都附带一个异常表。异常表中,每一个条目代表一个异常处理器,并且由from指针、to指针和target指针以及所捕获的异常类型构成。这些指针的值是字节码索引(bytecode index,bci)用以定位字节码。其中from指针和to指针标示了该异常处理器所监控的范围,例如try代码块所覆盖的范围。target指针则指向异常处理器的起始位置,例如catch代码块的起始位置。

    当程序触发异常时,JVM会从上至下遍历异常表中的所有条目。当触发异常的字节码的索引值在某个异常表条目的监控范围内,JVM会判断所抛出的异常和该条目想要捕获的异常是否匹配。如果匹配,JVM会将控制流转移至该条目target指针指向的字节码。如果遍历完所有的条目,JVM仍未匹配到异常处理器,那么它会弹出当前方法所对应的java栈帧,并且在调用者(caller)中重复上述操作。在最坏的情况下,JVM需要遍历当前线程java栈上所有方法的异常表。

    finally代码块的编译比较复杂。当前版本java编译器的做法,是复制finally代码块的内容,分别放在try-catch代码块所有正常执行路径以及异常执行路径的出口中。针对异常执行路径,java编译器会生成一个或多个异常表条目,监控整个try-catch代码块,并且捕获所有种类的异常。这些异常表条目的target指针将指向将指向另一份复制的finally代码块。并且,在这个finally代码块的最后,java编译器会重新抛出所捕获得异常。

    最后

    感谢你看到这里,看完有什么的不懂的可以在评论区问我,觉得文章对你有帮助的话记得给我点个赞,每天都会分享java相关技术文章或行业资讯,欢迎大家关注和转发文章!

    相关文章

      网友评论

        本文标题:细节!关于java异常的总结,我还没见过比这更详细的

        本文链接:https://www.haomeiwen.com/subject/apbzrktx.html