第11章 异常

作者: yangsg | 来源:发表于2019-04-09 09:57 被阅读342次

    1. 异常体系

    1.1 异常的分类

    所有异常(Exception)和错误(Error)的超类是问题类(Throwable,有些人也管它叫做异常)。
    问题(Throwable)主要分为两种:

    异常继承体系
    • 错误(Error)
      错误的类名以Error作为后缀,比如:StackOverflowError(栈溢出)。一般情况下认为Error是“硬伤”,由于硬件原因造成的程序的出错。物理上的问题是无法通过软件进行修复。

    • 异常(Exception)
      异常的类名以Exception作为后缀,一般情况下认为Exception是软件上的逻辑错误或者设计不周,是可以通过程序进行恢复的。管理异常其实指的是管理这部分内容。

    • 异常(Exception)内部的分类

    1. 运行时异常(非检查性异常) - RuntimeException
      运行时异常:编译时不报错,无需进行异常处理,但运行时报错。
      编写时不会发生错误,但运行时数组下标越界
    int[] a = {1,2,3};
    a[33] = 12;
    

    常见的运行时异常

    • java.lang.ArrayIndexOutOfBoundsException 数组下标越界
    • java.lang.IndexOutOfBoundsException 下标越界
    • java.lang.NullPointerException 空指针异常
    • java.lang.ArithmeticException 除数为0异常
    • java.lang.ClassCastException 引用数据类型类型转换异常(下溯造型中)

    运行时异常主要通过正确地编写程序逻辑来进行规避。

    1. 检查性异常 - 非Runtime继承体系下的异常,比如IOException...
      检查性异常:要求程序必须给出异常的处理办法(可以上报也可以处理),否则程序编译不通过无法执行。(虽然要求强制处理异常,但异常不一定发生)
      常见的的检查性异常
    • IOException 输入输出的异常
    • DataFormatException 数据格式异常
    • ParseException 转换异常(日期类型与字符串类型)
    • SQLException SQL语句和数据库操作异常

    笔试题1:异常的分类?
    主要是错误(Error)和异常(Exception),错误指的是硬件问题,通过代码无法修复。异常指的是逻辑问题,可以通过代码进行修复。
    笔试题2:Error和Exception的区别?
    错误指的是硬件问题,通过代码无法修复。异常指的是逻辑问题,可以通过代码进行修复。
    笔试题3:运行时异常和检查性异常的区别?
    运行时异常(RuntimeException)在编译时不报错,可以不进行处理,但运行时可能报错。
    检查性异常在编译时报错,要求必须进行异常处理,否则程序不能正常执行,但实际运行时可能不出错。
    笔试题4:写出了解的四种运行时异常

    • java.lang.IndexOutOfBoundsException 下标越界
    • java.lang.NullPointerException 空指针异常
    • java.lang.ArithmeticException 除数为0异常
    • java.lang.ClassCastException 引用数据类型类型转换异常(下溯造型中)

    2. 异常处理

    Java中的对于异常态度:管理异常。异常被作为类来看待

    2.1 上报(抛出)

    对于检查性异常,可以在方法中不进行处理,通过在方法上声明throws表示上报某个异常类型,一个方法可以上报多种异常。

    public void test() throws InterruptedException, FileNotFoundException {
        Thread.sleep(1000);
        InputStream is = new FileInputStream("c:/1.txt");
    }
    

    InputStream is = new FileInputStream("c:/1.txt");会上报一个FileNotFoundException
    is.read();会上报一个IOException
    IOException是FileNotFoundException的父类,所以两种异常合并成一种

    public void test2() throws IOException {
        InputStream is = new FileInputStream("c:/1.txt");
        is.read();
    }
    

    也可以通过throw的关键字,人为制造异常。
    示例1:add方法完成的是两个正整数相加,如果参数为负数,人为制造异常抛出

    public int add(int a, int b) throws Exception {
        if(a < 0 || b < 0) {
            Exception e = new Exception();
            throw e;
        }
        return a+b;
    }
    

    上报异常的目的是让方法的调用者了解方法可能出现的问题,进而有调用者决定是否处理异常。
    如果调用者继续上报,直至最终处理为止。如果整个调用环节所有的方法对异常都上报而不处理,最终异常会被抛给Java虚拟机,当异常发生时无人进行程序恢复操作,程序会在控制台报错。
    正常编写代码时,应该有一段程序完成异常的处理。
    笔试题/面试题:throw和throws的区别
    throw是人为制造异常的关键字
    throws是声明方法抛出异常类型的关键字

    2.2 处理

    处理异常的目的是当异常发生时,程序给予一些处理异常的代码,恢复程序的正常执行。
    处理异常的方式是try...catch...两个代码块
    语法:

    try{
    可能发生异常的代码
    }catch(异常类型 对象){
    当发生异常时的处理代码
    }

    当代码行B发生异常时,程序执行ABDE
    当try中的代码没有发生异常时,程序执行ABC,不会执行catch中的代码

    try{
        A
        B
        C
    }catch(异常类型 对象){
        D
        E
    }
    

    一个try块可以与多个catch块进行结合

    public class Test2 {
        public static void main(String[] args) {
            try {
                test();
            } catch (FileNotFoundException e) {
                System.out.println("所选文件没有找到");
            } catch (InterruptedException e) {
                System.out.println("线程运行失败");
            }
            
        }   
        public static void test() throws InterruptedException, FileNotFoundException {
            Thread.sleep(1000);
            InputStream is = new FileInputStream("c:/1.txt");
        }
    }
    

    但需要注意的是:如果多个异常存在着继承关系,catch的顺序是"由小到大"排列的

    public class Test2 {
    
        public static void main(String[] args) {
            try {
                test2();
            } catch (FileNotFoundException e) {
                System.out.println("文件没有找到");
            } catch (IOException e) {
                System.out.println("文件读写失败");
            } catch (Exception e) {
                System.out.println("发生了严重的问题");
            }
        }
        public static void test2() throws FileNotFoundException, IOException, Exception {
            InputStream is = new FileInputStream("c:/1.txt");
            is.read();
            throw new Exception();
        }
    }
    

    catch的顺序不能调整,因为三者存在继承关系:
    FileNotFoundException -> IOException -> Exception
    越特殊的异常应该放在上面,否则捕获不到特殊的异常,程序会报错。

    多个异常也可以通过同一个catch进行处理

    public class Test2 {
        public static void main(String[] args) {
            try {
                test();
            } catch (FileNotFoundException | InterruptedException e) {
                System.out.println("程序发生了问题");
            }       
        }   
        public static void test() throws InterruptedException, FileNotFoundException {
            Thread.sleep(1000);
            InputStream is = new FileInputStream("c:/1.txt");
        }
    }
    
    2.3 finally块

    finally块表示异常处理中的最终块,其体现的意义是:无论程序是否发生异常,是否遇到了return语句,这部分代码都必须执行。除非在try中或者catch中执行了关闭Java虚拟机的代码(System.exit(0)或System.exit(-1))
    语法:

    try{
    }
    可能有n个catch... (n >= 0)
    finally{
    }

    try...catch...finally的组合

    • try...catch...
    • try...catch...finally...
    • try...finally...

    当代码行B发生异常时,代码执行顺序:ABDEF
    当try中的代码没有发生异常时,代码执行顺序:ABCF

    try{
        A
        B
        C
    }catch(Exception e){
        D
        E
    }finally{
        F
    }
    

    在finally中完成资源回收,关闭数据库连接等收尾工作。
    如果不完成类似的操作,比如在try中打开了数据库连接,没有在finally中进行关闭连接操作,一旦try中发生异常,连接不能关闭就被浪费。随着这段程序被多次运行,每次都会打开新连接,直至数据库连接耗尽。

    public class Test1 {
        public static void main(String[] args) {
            int x = haha();
            System.out.println(x);
        }   
        public static int haha() {
            try {
                System.out.println(5/0);
                System.out.println("执行了try");
                return 1;   
            }catch (Exception e) {
                System.out.println("执行了catch");
                return 2;
            }finally {
                System.out.println("执行了finally");
            }
        }
    }
    

    代码的执行结果


    运行结果

    System.out.println(5/0);会发生异常,程序转到catch块中执行,在catch块return前,虚拟机会将finally中的代码放在return前执行,所以得到了上面的结果。

    如果在上述代码的finally中添加“return 3”,程序的运行结果是3,finally中的代码先完成return,其余块中return语句相当于无效了

    3. 发生异常时的堆栈信息

    默认在生成的catch块中有如下一句代码

    e.printStackTrace();
    

    这句代码的作用是打印方法栈的当前信息
    示例2:打印方法栈中的出错信息

    public class Test2 {
        public static void main(String[] args) {
            a();
            
        }   
        public static void a() {
            b();
        }   
        public static void b() {
            c();
        }   
        public static void c() {
            try {
                InputStream is = new FileInputStream("c:/abcsss.txt");
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }
    }
    

    运行结果


    异常出错的方法栈信息

    找到方法栈信息中第一行你自己编写代码,问题一般就在这个位置。
    这个示例中,出错的就是Test2类的c方法,问题是在Test2.java文件的24行
    定位到位置后发现其实"c:/abcsss.txt"文件不存在

    4. 自定义异常

    编写一个类,继承java.lang.Exception或者它的子类。
    示例3:制作一个参数为负数时的异常
    异常类

    public class NegativeNumberException extends Exception {
    }
    

    测试类

    public class Test {
        
        public static int add(int a, int b) throws NegativeNumberException {
            if(a < 0 || b < 0) {
                throw new NegativeNumberException();
            }
            return a+b;
        }
        public static void main(String[] args) {
            try {
                int x = add(-3, 5);
                System.out.println(x);
            } catch (NegativeNumberException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    

    相关文章

      网友评论

        本文标题:第11章 异常

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