异常处理

作者: 若兮缘 | 来源:发表于2018-12-19 21:29 被阅读63次

    try-catch-finally

    语法格式:

    try块:用于捕获异常
    catch块:用于处理try捕获到的异常
    finally块:无论是否发生异常代码总能执行
    try块后可接零个或多个catch块,如果没有catch块,则必须跟一个finally

    执行流程:
    1.我们将可能产生异常的代码放入try块当中,当try块中代码没有异常发生时,catch块中的内容不会被执行,而直接执行之后的代码。
    2.当try块发生异常时,会产生一个异常对象且当该类型能与catch块中的异常类型正常匹配时,程序就会进入到catch块执行相应的处理逻辑,然后顺序执行下去。
    3.当出现异常且无法正常匹配处理则程序中断运行

    示例:

    public class TryDemo {
        public static void main(String[] args) {
            //定义两个整数,接受用户的键盘输入,输出两数之商
            Scanner input=new Scanner(System.in);
            System.out.println("=====运算开始=====");
            System.out.print("请输入第一个整数:");
            int one=input.nextInt();
            System.out.print("请输入第二个整数:");
            int two=input.nextInt();
            System.out.println("one和two的商是:"+ (one/two));
            System.out.println("=====运算结束=====");
        }
    }
    

    程序运行结果:
    当我们输入的是非数字时,程序会报错并终止运行,报错的异常为InputMismatchException
    当我们输入的第二个数为0时,程序也会报错并终止运行,报错的异常为ArithmeticException

    使用try-catch-finally对异常进行捕获处理

    public class TryDemo {
        public static void main(String[] args) {
            //定义两个整数,接受用户的键盘输入,输出两数之商
            Scanner input=new Scanner(System.in);
            try {
                System.out.println("=====运算开始=====");
                System.out.print("请输入第一个整数:");
                int one=input.nextInt();
                System.out.print("请输入第二个整数:");
                int two=input.nextInt();
                System.out.println("one和two的商是:"+ (one/two));
            } catch (Exception e) {
                System.out.println("程序出错啦~~~~");
                e.printStackTrace(); //打印错误的堆栈信息
            }finally{
                System.out.println("=====运算结束=====");
            }
        }
    }
    

    此时如果发生上述任意一种错误时,程序会执行catch块中的处理逻辑,然后继续往下执行直到程序结束;
    这里catch块中的异常类型为Exception,是所有可处理异常类的基类,所以出现任何类型异常都能匹配上;
    而定义在finally块中的逻辑一定会被执行,不管程序是否出现异常。

    使用多重catch结构处理异常

    当程序可能会产生多种类型的异常,针对可能出现的不同异常如果希望做不同的处理,那么就可以使用多重catch
    注意多重catch块中的异常类型不能一致,且捕获父类型的catch块应该在子类型的后面,比如Exception应该在最后面

    注意事项:
    1.一旦某个catch捕获到匹配的异常类型,将进入异常处理代码。一经处理结束,就意味着整个try-catch语句结束,其他的catch子句不再有匹配和捕获异常类型的机会。
    2.对于有多个catch子句的异常程序而言,应该尽量将捕获底层异常类的catch子句放在前面,同时尽量将捕获相对高层的异常类的catch子句放在后面。否则,捕获底层异常类的catch子句将可能会被屏蔽。

    示例:

    public class TryDemo {
        public static void main(String[] args) {
            //定义两个整数,接受用户的键盘输入,输出两数之商
            Scanner input=new Scanner(System.in);
            try{
                System.out.print("请输入第一个整数:");
                int one=input.nextInt();
                System.out.print("请输入第二个整数:");
                int two=input.nextInt();
                System.out.println("one和two的商是:"+ (one/two));
            }catch(ArithmeticException e){
                System.exit(1);
                System.out.println("除数不允许为零");
                e.printStackTrace();
            }catch(InputMismatchException e){
                System.out.println("请输入整数");
                e.printStackTrace();
            }catch(Exception e){ //位置应该在最后一个catch块中
                //如果出现的异常没有被前面的catch块匹配,那么在此处进行处理
                System.out.println("出错啦~~");
                e.printStackTrace();
            }finally{
                System.out.println("=====运算结束=====");
            }
        }
    }
    
    终止finally执行的方法

    上面的程序如果第二个输入的数字为0的话,那么程序就终止了,没有任何打印输出。
    因为当执行System.exit(1)方法就表示程序无条件终止运行,后面的finally也就不会被执行

    return的说明

    当代码中出现return时,一定是finally语句块执行完成后才会去执行相应的return代码,无论return语句在什么位置。


    示例:
    public class TryDemo {
        public static void main(String[] args) {
            int result=test();
            System.out.println("one和two的商是:"+ result);
            
        }
        public static int test(){
            Scanner input=new Scanner(System.in);
            System.out.println("=====运算开始=====");
            try{
                System.out.print("请输入第一个整数:");
                int one=input.nextInt();
                System.out.print("请输入第二个整数:");
                int two=input.nextInt();
                return one/two;
            }catch(ArithmeticException e){
                System.out.println("除数不允许为零");
                return 0;
            }finally{
                System.out.println("=====运算结束=====");
                return -100000; //最好不要在finally语句中返回方法结果
            }
        }
    }
    

    运行结果:
    当输入的两个数为36和6时,程序的执行结果为:one和two的商是:-10000
    当输入的两个数为36和0时,程序的执行结果为:one和two的商是:-10000
    所以当try/catchfinally同时存在return语句返回方法值时,无论程序是否正常执行,最终返回的都是finally的结果

    throw和throws

    可以通过throws声明将要抛出何种类型的异常,通过throw将产生的异常抛出

    throws

    throws语句用在方法定义时声明该方法要抛出的异常类型
    如果一个方法可能会出现异常,但不想或者没有能力处理这种异常,可以在方法声明处用throws子句来声明抛出异常
    当方法抛出异常时,方法将不对这些类型及其子类类型的异常作处理,而抛向调用该方法的方法,由他去处理

    使用规则:
    1.如果是不可查异常(unchecked exception),即Error、RuntimeException或它们的子类,那么可以不使用throws关键字来声明要抛出的异常,编译仍能顺利通过,但在运行时会被系统抛出
    2.如果一个方法中可能出现可查异常,要么用try-catch语句捕获,要么用throws子句声明将它抛出,否则编译出错
    3.当抛出了检查异常时,则该方法的调用者必须处理或者重新抛出该异常
    4.当子类重写父类抛出异常的方法时,声明的异常必须是父类方法所声明异常的同类或子类

    通过throws抛出异常的解决方案:
    1.throws后面接多个异常类型,中间用逗号分隔
    2.throws后面接Exception,表示任何异常都向外抛出

    注意事项:
    根据throws抛出的异常类型来决定调用者是否必须处理该异常,如果是非检查异常则可以不处理,而如果是检查异常,则必须捕获处理,而Exception也包含了非检查异常,所以也必须进行处理。
    对于方法只抛出非检查异常我们可以通过文档注释来标识方法抛出的异常,这样对调用者更友好。

    示例:

    import java.util.InputMismatchException;
    import java.util.Scanner;
    
    public class TryDemo {
        public static void main(String[] args) {
            //在Eclipse中可以选中代码通过右键Surround With -> Try/Catch Block完成处理异常的代码
            //方案一:针对方法抛出的多个异常进行捕获处理,此时可以只选择捕获处理其中一个异常
            try {
                int result=test();
                System.out.println("one和two的商是:"+ result);
            } catch (ArithmeticException e) {
                System.out.println("除数不允许为零");
                e.printStackTrace();
            } catch (InputMismatchException e){
                System.out.println("请输入整数");
                e.printStackTrace();
            }
            //方案二:如果方法抛出的是Exception类型,那么catch块中必须包含对Exception的处理,其他子类异常处理是可选的
            try{
                int result = test2();
                System.out.println("one和two的商是:" + result);
            }catch(ArithmeticException e){
                
            }catch(InputMismatchException e){
                
            }catch(Exception e){//这个catch块必须包含
                
            }
        }
    
        //方案一:throws后面接多个异常类型,中间用逗号分隔
        /**
         * 使用文档注释给出异常提示
         * @return
         * @throws ArithmeticException
         * @throws InputMismatchException
         */
        public static int test() throws ArithmeticException, InputMismatchException{
            Scanner input=new Scanner(System.in);
            System.out.println("=====运算开始=====");
            System.out.print("请输入第一个整数:");
            int one=input.nextInt();
            System.out.print("请输入第二个整数:");
            int two=input.nextInt();
            System.out.println("=====运算结束=====");
            return one / two;
        }
        //方案二:throws后面接Exception,表示任何异常都向外抛出
        public static int test2() throws Exception{
            Scanner input=new Scanner(System.in);
            System.out.println("=====运算开始=====");
            System.out.print("请输入第一个整数:");
            int one=input.nextInt();
            System.out.print("请输入第二个整数:");
            int two=input.nextInt();
            System.out.println("=====运算结束=====");
            return one / two;
        }
    }
    

    throw

    throw用来抛出一个异常,例如:throw new IOException();
    throw抛出的只能够是可抛出类Throwable或者其子类的实例对象。如:throw new String("出错了");是错误的

    throw抛出异常对象的处理方案
    1.在throw语句外面套上try-catch块,自己抛出的异常自己处理
    2.通过throws在方法声明处抛出异常类型,谁调用方法谁处理,调用者可以自己处理,也可以继续上抛,此时可以抛出与throw对象相同的类型或者其父类

    异常抛出与处理作用
    1.规避可能出现的风险
    2.完成一些程序的逻辑

    示例:完成酒店入住限定的场景

    public class TryDemo {
        public static void main(String[] args) {
            try {
                testAge(); 
            }catch(Exception e){
                //当发生异常时打印:java.lang.Exception: 18岁以下,80岁以上的住客必须由亲友陪同
                e.printStackTrace();
            }
        }
        // 描述酒店的入住规则:限定年龄,18岁以下,80岁以上的住客必须由亲友陪同
        public static void testAge() {
            try {
                System.out.println("请输入年龄:");
                Scanner input = new Scanner(System.in);
                int age = input.nextInt();
                if (age < 18 || age > 80) {
                    throw new Exception("18岁以下,80岁以上的住客必须由亲友陪同");
                } else {
                    System.out.println("欢迎入住本酒店");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

    自定义异常

    • 使用Java内置的异常类可以描述在编程时出现的大部分异常情况。
    • 也可以通过自定义异常描述特定业务产生的异常类型。
    • 所谓自定义异常,就是定义一个类,去继承Throwable类或者它的子类。

    示例:我们用自定义异常去描述不满足酒店入住规则的情况

    /**
     * 自定义异常
     */
    public class HotelAgeException extends Exception {
        public HotelAgeException(){
            super("18岁以下,80岁以上的住客必须由亲友陪同");
        }
    }
    //测试类
    public class TryDemo {
        public static void main(String[] args) {
            try {
                testAge(); 
            } catch (HotelAgeException e) {
                System.out.println(e.getMessage()); //18岁以下,80岁以上的住客必须由亲友陪同
                System.out.println("酒店前台工作人员不允许办理入住登记");
            }catch(Exception e){
                e.printStackTrace();
            }
        }
        // 描述酒店的入住规则:限定年龄,18岁以下,80岁以上的住客必须由亲友陪同
        public static void testAge() throws HotelAgeException {
            System.out.println("请输入年龄:");
            Scanner input = new Scanner(System.in);
            int age = input.nextInt();
            if (age < 18 || age > 80) {
                throw new HotelAgeException(); //抛出自定义异常
            } else {
                System.out.println("欢迎入住本酒店");
            }
        }
    }
    

    异常链

    有时候我们会捕获一个异常后再抛出另一个异常。此时异常会如何显示呢?

    示例:有三个方法testOne、testTwo、testThree,依次进行调用捕获异常并抛出一个新的异常

    public class TryDemo {
    
        public static void main(String[] args) {
            try {
                testThree();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        public static void testOne() throws HotelAgeException {
            throw new HotelAgeException();
        }
    
        public static void testTwo() throws Exception {
            try {
                testOne();
            } catch (HotelAgeException e) {
                throw new Exception("我是新产生的异常1");
            }
        }
    
        public static void testThree() throws Exception {
            try {
                testTwo();
            } catch (Exception e) {
                throw new Exception("我是新产生的异常2");
            }
        }
    }
    

    运行结果:
    只显示了最后抛出的异常2,前面两个方法的异常信息是丢失的,这就是一种由于新抛出异常导致异常信息丢失的场景,如何将前面的异常信息也保留下来呢,java就提供了这种保留异常信息的机制。

    解决方案:
    1.可以通过构造方法将异常对象作为参数去构造新的异常对象
    2.调用Throwable提供的initCause方法

    修改代码:

        public static void testTwo() throws Exception {
            try {
                testOne();
            } catch (HotelAgeException e) {
                //通过构造方法保留异常信息
                throw new Exception("我是新产生的异常1", e);
            }
        }
    
        public static void testThree() throws Exception {
            try {
                testTwo();
            } catch (Exception e) {
                Exception e1=new Exception("我是新产生的异常2");
                //通过initCause方法保留异常信息
                e1.initCause(e);
                throw e1;
    //          throw new Exception("我是新产生的异常2",e);
            }
        }
    }
    

    运行结果:

    异常链

    当捕获一个异常后再抛出另一个异常时,如果希望将异常发生的原因一个传一个串起来,即把底层的异常信息传给上层,这样逐层抛出而形成的一种链条

    经验与总结

    • 处理运行时异常时,采用逻辑去合理规避同时辅助try-catch处理
    • 在多重catch块后面,可以加一个catch(Exception)来处理可能会被遗漏的异常
    • 对于不确定的代码,也可以加上try-catch,处理潜在的异常
    • 尽量去处理异常,切忌只是简单的调用printStackTrace()去打印输出
    • 具体如何处理异常,要根据不同的业务需求和异常类型去决定
    • 尽量添加finally语句块去释放占用的资源

    相关文章

      网友评论

        本文标题:异常处理

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