Java异常处理 - try...catch

阅读:298

目录介绍

  • 01.try...catch处理异常
  • 02.try...catch注意事项
  • 03.try-catch-finally规则
  • 04.没有捕获异常分析
  • 05.try...catch使用建议
  • 06.catch执行注意要点
  • 07.finally一定会执行吗
  • 08.try...catch语句块的执行顺序
  • 09.问题分析与思考
  • 10.了解try和catch基本用法
  • 11.多条catch子句

01.try...catch处理异常

  • try...catch处理异常的基本格式
    try    {
        可能出现问题的代码 ;
    }catch(异常名 变量名){
        针对问题的处理 ;
    }finally{
        释放资源;
    }
    

  • 作用说明
    • try 块:用于捕获异常。其后可接零个或多个catch块,如果没有catch块,则必须跟一个finally块。
    • catch 块:用于处理try捕获到的异常。
    • finally 块:无论是否捕获或处理异常,finally块里的语句都会被执行。有些情况则不会执行,下面会说到。

02.try...catch注意事项

  • 注意事项:
    • a:try中的代码越少越好
    • b:catch中要做处理,哪怕是一条输出语句也可以.(不能将异常信息隐藏)
    • c:处理多个异常
      • 1:能明确的尽量明确,不要用大的来处理。
      • 2:平级关系的异常谁前谁后无所谓,如果出现了子父关系,父必须在后面。

03.try-catch-finally规则

  • try-catch-finally规则如下所示:
      1. 必须在 try 之后添加 catch 或 finally 块。try 块后可同时接 catch 和 finally 块,但至少有一个块。
      1. 必须遵循块顺序:若代码同时使用 catch 和 finally 块,则必须将 catch 块放在 try 块之后。
      1. catch 块与相应的异常类的类型相关。
      1. 一个 try 块可能有多个 catch 块。若如此,则执行第一个匹配块。即Java虚拟机会把实际抛出的异常对象依次和各个catch代码块声明的异常类型匹配,如果异常对象为某个异常类型或其子类的实例,就执行这个catch代码块,不会再执行其他的 catch代码块
      1. 可嵌套 try-catch-finally 结构。
      1. 在 try-catch-finally 结构中,可重新抛出异常。
      1. 除了下列情况,总将执行 finally 做为结束:JVM 过早终止(调用 System.exit(int));在 finally 块中抛出一个未处理的异常;计算机断电、失火、或遭遇病毒攻击。

04.没有捕获异常分析

  • 程序明明出现了异常,也catch(Exception e)了,却没有捕获到任何信息。
    • 原因无非有两个:
      • 1.异常所在的线程跟你捕获的线程不是同一个线程;
      • 2.程序抛出的不是Exception而是Error。Error跟Exception一样都继承自Throwable,是指不应该被捕获的严重错误。
    • 为什么不该捕获Error呢?
      • 因为出现Error的情况会造成程序直接无法运行,所以捕获了也没有任何意义
  • 发生这种问题的使用场景

05.try...catch使用建议

  • 为什么开发中建议开发者捕获异常而不要抛出异常?

06.catch执行注意要点

  • 一旦某个catch捕获到匹配的异常类,其它的catch还会执行吗?
    • 一旦某个catch捕获到匹配的异常类型,将进入异常处理代码。一经处理结束,就意味着整个try-catch语句结束。其他的catch子句不再有匹配和捕获异常类型的机会。
    • 对于有多个catch子句的异常程序而言,应该尽量将捕获底层异常类的catch子 句放在前面,同时尽量将捕获相对高层的异常类的catch子句放在后面。否则,捕获底层异常类的catch子句将可能会被屏蔽。
  • 举个例子:
    • RuntimeException异常类包括运行时各种常见的异常,ArithmeticException类和ArrayIndexOutOfBoundsException类都是它的子类。因此,RuntimeException异常类的catch子句应该放在 最后面,否则可能会屏蔽其后的特定异常处理或引起编译错误。博客

07.finally一定会执行吗

  • finally是异常处理的统一出口,常用来实现资源释放,比如关闭文件,关于数据库连接等。不一定会被执行的。
  • 在以下4种特殊情况下,finally块不会被执行:
    • 1.在finally语句块中发生了异常。
    • 2.在前面的代码中用了System.exit()退出程序。
    • 3.程序所在的线程死亡。
    • 4.关闭CPU。

08.try...catch语句块的执行顺序

  • try-catch-finally的语句块的执行顺序:
    • 1)当try没有捕获到异常时:try语句块中的语句逐一被执行,程序将跳过catch语句块,执行finally语句块和其后的语句;
    • 2)当try捕获到异常,catch语句块里没有处理此异常的情况:当try语句块里的某条语句出现异常时,而没有处理此异常的catch语句块时,此异常将会抛给JVM处理,finally语句块里的语句还是会被执行,但finally语句块后的语句不会被执行;
    • 3)当try捕获到异常,catch语句块里有处理此异常的情况:在try语句块中是按照顺序来执行的,当执行到某一条语句出现异常时,程序将跳到catch语句块,并与catch语句块逐一匹配,找到与之对应的处理程序,其他的catch语句块将不会被执行,而try语句块中,出现异常之后的语句也不会被执行,catch语句块执行完后,执行finally语句块里的语句,最后执行finally语句块后的语句;

09.问题分析与思考

  • 先来看一个题目,看一下A和B有何区别,哪一个性能更好一些?为什么?
    • A代码
    try {
        for(int i = 0; i < max; i++) {
            String myString = ...;
            float myNum = Float.parseFloat(myString);
            myFloats[i] = myNum;
        }
    } catch (NumberFormatException ex) {
        return null;
    }
    

    • B代码
    for(int i = 0; i < max; i++) {
        String myString = ...;
        try {
            float myNum = Float.parseFloat(myString);
        } catch (NumberFormatException ex) {
            return null;
        }
        myFloats[i] = myNum;
    }
    

  • A和B的区别
    • 其实在功能上明显有区别,一个for循环中出现一个异常就终止,另一个则是会很负责任的把它全部跑完,但问题不是问功能,而是问性能上的区别。
  • 性能问题分析
    • 性能无非就是看 空间消耗,时间消耗,想当然就觉得try……catch重复执行了这么多次肯定比只执行了一次跑得肯定慢,空间消耗肯定更大。好吧,哥承认完全不了解其中的工作原理。
  • 讨论的结果是
    • 在没有抛出异常的情况下,性能完全没区别。
  • 原因分析
    • 类会跟随一张 异常表(exception table),每一个try……catch都会在这个表里添加行记录,每一个记录都有4个信息(try catch的开始地址,结束地址,异常的处理起始位,异常类名称)。
    • 当代码在运行时抛出了异常时,首先拿着抛出位置到异常表中查找是否可以被catch(例如看位置是不是处于任何一栏中的开始和结束位置之间),如果可以则跑到异常处理的起始位置开始处理,如果没有找到则原地return,并且copy异常的引用给父调用方,接着看父调用的异常表。。。以此类推。
    • 异常如果没发生,也就不会去查表,也就是说你写不写try catch 也就是有没有这个异常表的问题,如果没有发生异常,写try catch对性能是木有消耗的,所以不会让程序跑得更慢。try 的范围大小其实就是异常表中两个值(开始地址和结束地址)的差异而已,也是不会影响性能的。

10.了解try和catch基本用法

  • 看一下下面案例代码
    public class YC {
        public static void main(String[] args) {
            try {
                int i = 10/0;
                System.out.println("i="+i); 
            } catch (ArithmeticException e) {
                System.out.println("Caught Exception"); 
                System.out.println("e.getMessage(): " + e.getMessage()); 
                System.out.println("e.toString(): " + e.toString()); 
                System.out.println("e.printStackTrace():");
                e.printStackTrace(); 
            }
        }
    }
    

  • 运行结果
    Caught Exception
    e.getMessage(): / by zero
    e.toString(): java.lang.ArithmeticException: / by zero
    e.printStackTrace():
    java.lang.ArithmeticException: / by zero
        at YC.main(YC.java:6)
    

  • 结果说明:
    • 在try语句块中有除数为0的操作,该操作会抛出java.lang.ArithmeticException异常。通过catch,对该异常进行捕获。观察结果我们发现,并没有执行System.out.println("i="+i)。这说明try语句块发生异常之后,try语句块中的剩余内容就不会再被执行了。

11.多条catch子句

  • 在有些情况下,一个代码块可能会引发多个异常。对于这种情况,需要指定两条或多条catch子句,用于捕获不同类型的异常。当抛出异常时,按顺序检查每条catch语句,执行类型和异常相匹配的第一条catch子句,忽略其他catch子句,并继续执行try/catch代码块后面的代码。
    • 需要注意的是,异常子类必须位于异常超类之前,因为使用了某个超类的catch语句会捕获这个超类及其所有子类的异常。因此,如果子类位于超类之后的话,永远也不会到达子类。不可到达的代码会被编译器提示错误。
    • 参照如下代码,通过Math的静态方法random来随机产生0和1两个随机数,生成的不同数值就会引发不同的异常,分别由不同的catch子句进行处理
    public static void main(String[] args) {	
    	int random=(int) Math.round(Math.random());
    	try{
    		int a=10/random;
    		int[] array={10};
    		array[random]=1;
    	}catch (ArithmeticException e) {
    		System.out.println("ArithmeticException");
    	}catch (ArrayIndexOutOfBoundsException e) {
    		System.out.println("ArrayIndexOutOfBoundsException");
    	}
    	System.out.println("代码块结束");
    }
    

  • 此外,也可以通过多重捕获的方式来使用相同的catch子句捕获两个或更多个异常。在catch子句中使用或运算符(|)分隔每个异常,每个多重捕获参数都被隐式地声明为final类型。
    public static void main(String[] args) {    
        int random=(int) Math.round(Math.random());
        try{
            int a=10/random;
            int[] array={10};
            array[random]=1;
        }catch(ArithmeticException | ArrayIndexOutOfBoundsException e){
        	System.out.println("两个异常之一");
        }
    }

赞赏支持


精彩留言

发表评论