JAVA异常的捕获与处理
视频链接:https://edu.aliyun.com/lesson_1011_8939#_8939
java语言提供最为强大的支持就在于异常的处理操作上。
1,认识异常对程序的影响
异常指的是导致程序中断的一种指令流。下面先看没有异常产生的程序执行结果
范例:没有异常产生
1 public class Main { 2 public static void main(String[] args) { 3 System.out.println("******* 程序开始执行 *******"); 4 System.out.println("******* "+10/2); 5 System.out.println("******* 程序执行结束 *******"); 6 } 7 }
1 ******* 程序开始执行 ******* 2 ******* 5 3 ******* 程序执行结束 *******
在程序执行正常的过程里面会发现,所有的程序会按照既定的结构从头到尾开始执行。
范例:产生异常
1 public class Main { 2 public static void main(String[] args) { 3 System.out.println("******* 程序开始执行 *******"); 4 System.out.println("******* "+10/0); 5 System.out.println("******* 程序执行结束 *******"); 6 } 7 }
1 ******* 程序开始执行 ******* 2 3 Exception in thread "main" java.lang.ArithmeticException: / by zero 4 5 at Main.main(Main.java:4)
在出现错误之后,整个程序将不会按照既定的方式进行执行,而是中断了执行。那么为了保证程序出现了非致命错误后程序依然可以正常完成,所以就需要有一个完善的异常处理机制,以保证程序的顺利执行。
2,处理异常
在java中如果要进行异常处理,可以使用:try、catch、finally关键字。
结构:
1 try { 2 3 //可能出现异常的语句 4 5 }[catch (异常类型 异常对象)] 6 7 //异常处理 8 9 }catch (异常类型 异常对象)] 10 11 //异常处理 12 13 }catch(异常类型 异常对象)] 14 15 //异常处理 16 17 }...][finally { 18 19 //不管异常是否处理执行,本句都要执行; 20 21 }]
范例:异常处理在此格式之中可以使用的组合为:①try...catch;②try...catch...finally;③try...finally。
1 public class Main { 2 public static void main(String[] args) { 3 System.out.println("******* 程序开始执行 *******"); 4 try{ 5 System.out.println("******* "+10/0); 6 }catch (ArithmeticException e){ 7 System.out.println("异常处理:"+e); 8 } 9 10 System.out.println("******* 程序执行结束 *******"); 11 } 12 }
1 ******* 程序开始执行 ******* 2 异常处理:java.lang.ArithmeticException: / by zero 3 ******* 程序执行结束 *******
范例:使用printStackTrace()输出完整的异常信息现在可以发现即便出现了异常,我们的程序也可以正常执行完毕,所以此时的设计属于一个合理设计。但是有一个问题出现了:此时在进行异常处理的时候直接输出的是一个异常类的对象,那么对于此对象如果直接打印(调用toString()所得到的异常信息并不完整,那么如果想要获得非常完整的异常信息,则可以使用异常类中提供的printStackTrace()方法
1 public class Main { 2 public static void main(String[] args) { 3 System.out.println("******* 程序开始执行 *******"); 4 try{ 5 System.out.println("******* "+10/0); 6 }catch (ArithmeticException e){ 7 e.printStackTrace(); 8 } 9 System.out.println("******* 程序执行结束 *******"); 10 } 11 }
1 ******* 程序开始执行 ******* 2 java.lang.ArithmeticException: / by zero 3 at Main.main(Main.java:5) 4 ******* 程序执行结束 *******
范例:使用finally语句对于异常处理格式也可以在最后追加一个finally程序块,表示异常处理后的出口,不管是否出现异常都处理。
1 public class Main { 2 public static void main(String[] args) { 3 System.out.println("******* 程序开始执行 *******"); 4 try{ 5 //System.out.println("******* "+10/0); 6 7 System.out.println("******* "+10/1); 8 }catch (ArithmeticException e){ 9 e.printStackTrace(); 10 }finally { 11 System.out.println("【不管是否出现异常都处理 finally】"); 12 } 13 System.out.println("******* 程序执行结束 *******"); 14 } 15 }
1 ******* 程序开始执行 ******* 2 java.lang.ArithmeticException: / by zero 3 at Main.main(Main.java:5) 4 【不管是否出现异常都处理 finally】 5 ******* 程序执行结束 *******
1 ******* 程序开始执行 ******* 2 ******* 10 3 【不管是否出现异常都处理 finally】 4 ******* 程序执行结束 *******
3,处理多个异常
很多时候在程序执行的过程之中可能会产生若干个异常,那么这种情况下也可以使用多个catch进行异常捕获。现在假设通过初始化的参数来进行来个计算数值的设置。
范例:多个异常catch捕获
1 public class Main { 2 public static void main(String[] args) { 3 System.out.println("******* 程序开始执行 *******"); 4 try{ 5 int x=Integer.parseInt(args[0]); 6 int y=Integer.parseInt(args[1]); 7 System.out.println("******* "+(x/y)); 8 }catch (ArrayIndexOutOfBoundsException e){ 9 e.printStackTrace(); 10 }catch (NumberFormatException e){ 11 e.printStackTrace(); 12 }catch (ArithmeticException e){ 13 e.printStackTrace(); 14 } 15 finally { 16 System.out.println("【不管是否出现异常都处理 finally】"); 17 } 18 System.out.println("******* 程序执行结束 *******"); 19 } 20 }
①【未处理】程序执行的时候没有输入初始化参数(java JavaDemo);java.lang.ArrayIndexOutOfBoundsException那么对于此时的程序就有可能产生三类异常:
②【未处理】输入的数据不是数字(java JavaDemo a b);
java.lang.NumberFormatException
③【已处理】输入的被除数为零(java JavaDemo 10 0);
java.lang.ArithmeticException
现在即便有了异常处理语句,但是如果没有进行正确的异常捕获,那么程序也会导致中断(finally的代码依然执行),所以在这样的情况下就必须进行多个异常的捕获
范例:多个异常捕获
1 public class Main { 2 public static void main(String[] args) { 3 System.out.println("******* 程序开始执行 *******"); 4 try{ 5 int x=Integer.parseInt(args[0]); 6 int y=Integer.parseInt(args[1]); 7 System.out.println("******* "+(x/y)); 8 }catch (ArrayIndexOutOfBoundsException e){ 9 e.printStackTrace(); 10 }catch (NumberFormatException e){ 11 e.printStackTrace(); 12 }catch (ArithmeticException e){ 13 e.printStackTrace(); 14 }finally { 15 System.out.println("【不管是否出现异常都处理 finally】"); 16 } 17 System.out.println("******* 程序执行结束 *******"); 18 } 19 }
此时我们开发已经知道有那些异常了,那么又何必要用那个异常呢???直接多写点判断不就可以了。
4,异常处理流程
在进行异常处理的时候如果将所有可能已经明确知道要产生的异常捕获,虽然你可以得到非常良好的代码结构,但是这种代码编写是非常麻烦的,所以现在要想进行合理异常就必须清楚在异常产生之后程序到底做了哪些处理。
①在程序运行的过程中才会产生异常,而一旦程序执行中产生了异常之后,将自动进行指定的异常类对象实例化处理;
②如果此时程序之中并没有提供有异常处理的支持,则会采用JVM默认异常处理方式,首先进行异常信息的打印,而后直接退出当前的程序;
③此时程序中如果有存在异常处理,那么这个产生的异常类的实例化对象将会被try语句捕获;
④try捕获到异常之后与其匹配的catch中的异常类型进行依次的比对,如果此时与catch中的捕获异常类型相同,则认为应该使用此catch进行异常处理,如果不匹配则继续匹配后续的catch类型,如果没有任何的catch匹配成功,那么就表示该异常无法进行处理;
⑤不管是否处理语句都要处理finally语句,但是当执行完成finally的程序之后会进一步判断当前的异常是否已经处理过了,如果处理过了,则继续向后执行其他代码,如果没有没有处理则交由JVM进行默认处理。
通过分析可以发现在整个的异常处理之中实际上操作的还是一个异常类的实例化对象的类型就成为了理解异常处理的核心关键所在,在之前接触过了两种异常:
ArithmeticException() |
ArrayIndexOutOfBoundsException |
java.lang.Object java.lang.Throwable java.lang.Exception java.lang.RuntimeException java.lang.ArithmeticException |
java.lang.Object java.lang.Throwable java.lang.Exception java.lang.RuntimeException java.lang.IndexOutOfBoundsException java.lang.ArrayIndexOutOfBoundsException |
可以发现在程序之中可以处理的异常最大的类型就是Throwable,而打开Throwable可以观察在此类中提供有两个子类
①Error:此时程序还未执行出现的错误,开发者无法处理;
②Exception:程序中出现的异常,开发者可以处理,真正在开发中需要关注的是Exception;
通过分析可以发现异常产生的时候会产生异常的实例化对象那么按照对象的应用原则,可以向父类转型,那么按照这样的逻辑,实际上所有的异常都可以使用Exception来处理。
范例:简化异常处理
1 public class Main { 2 public static void main(String[] args) { 3 System.out.println("******* 程序开始执行 *******"); 4 try{ 5 int x=Integer.parseInt(args[0]); 6 int y=Integer.parseInt(args[1]); 7 System.out.println("******* "+(x/y)); 8 }catch (Exception e){//最大范围的异常捕获 9 e.printStackTrace();} 10 finally { 11 System.out.println("【不管是否出现异常都处理 finally】"); 12 } 13 System.out.println("******* 程序执行结束 *******"); 14 } 15 }
在以后进行多个异常同时处理的时候要把捕获范围大的异常放在捕获范围小的异常之后。当你不确定可能产生那些异常的时候,这种处理方式是最方便的。但是如果这样处理也会产生一个问题,这种异常的处理形式虽然方便,但是它描述的错误信息不明确,所以分开处理异常是一种可以更加明确的处理方式。
5,throws关键字
通过之前的程序可以发现,在执行程序的过程中可能会产生异常,但是如果说现在假设你定义了一个方法,实际上就应该明确地告诉使用者,这个方法可能会产生何种异常,那么此时就可以在方法的声明上使用throws关键字。
范例:观察throws的使用
1 class MyMath{ 2 public static int div(int x,int y) throws Exception{ 3 return x/y; 4 } 5 } 6 public class Main001 { 7 public static void main(String args[]){ 8 try { 9 System.out.println(MyMath.div(10,0)); 10 } catch (Exception e) { 11 e.printStackTrace(); 12 }finally { 13 System.out.println("hello Mufasa"); 14 } 15 } 16 }
范例:在主方法上继续抛出异常主方法本身也是一个方法,那么实际上主方法也可以继续向上抛出。
1 class MyMath{ 2 public static int div(int x,int y) throws Exception{ 3 return x/y; 4 } 5 } 6 public class Main001 { 7 public static void main(String args[]) throws Exception{ 8 System.out.println(MyMath.div(10,0)); 9 } 10 }
如果主方法继续向上抛出异常,那么就表示此异常将交由JVM负责处理。
6,throw关键字(少s)
throw关键字,此关键字的主要作用在于表示手工进行异常的抛出,即:此时将手工产生一个异常类的实例化对象,并且进行异常的抛出处理。
范例:观察throw的使用
1 public class Main002 { 2 public static void main(String[] args) { 3 try {//异常对象不是由系统生成的,而是由手工定义的 4 throw new Exception("自己抛出一个异常"); 5 }catch (Exception e){ 6 e.printStackTrace(); 7 } 8 } 9 }
①throw:在代码块中使用的,主要是手工进行异常对象的抛出;面试题:请解释throws和throw的区别?
②throws:在方法定义上使用的,表示此方法中可能产生的异常明确告诉调用处,由调用处进行处理;
7,异常处理的标准格式
现在已经学习了大部分的异常处理格式:try、catch、finally、throw、throws,那么这些关键字在实际开发之中往往会一起进行使用,下面通过一个具体的程序进行分析。
现在要求定义一个可以实现除法计算的方法,在这个方法之中开发要求如下:
①在进行数学计算开始与结束的时候进行信息提示;
②如果在进行计算的过程之中产生了异常,则要交给调用处来处理。
class MyMath{ //异常要交给被调用处处理则一定要在方法上使用throw public static int div(int x,int y) throws Exception{ int temp=0; System.out.println("**** 【START】除法计算开始"); temp=x/y; System.out.println("**** 【END】除法计算结束");//有异常不出来 return temp; } } public class Main001 { public static void main(String args[]) { try { System.out.println(MyMath.div(10,0)); } catch (Exception e) { e.printStackTrace(); }finally { System.out.println("hello Mufasa"); } } }
1 **** 【START】除法计算开始 2 java.lang.ArithmeticException: / by zero 3 at MyMath.div(Main001.java:6) 4 at Main001.main(Main001.java:18) 5 hello Mufasa
1 class MyMath{ 2 //异常要交给被调用处处理则一定要在方法上使用throw 3 public static int div(int x,int y) throws Exception{ 4 int temp=0; 5 System.out.println("**** 【START】除法计算开始"); 6 try { 7 temp=x/y; 8 }catch (Exception e){ 9 e.printStackTrace(); 10 }finally { 11 System.out.println("**** 【END】除法计算结束"); 12 } 13 return temp; 14 } 15 } 16 public class Main001 { 17 public static void main(String args[]) { 18 try { 19 System.out.println(MyMath.div(10,0)); 20 } catch (Exception e) { 21 e.printStackTrace(); 22 }finally { 23 System.out.println("hello Mufasa"); 24 } 25 } 26 }
1 **** 【START】除法计算开始 2 3 java.lang.ArithmeticException: / by zero 4 5 at MyMath.div(Main001.java:7) 6 7 at Main001.main(Main001.java:23) 8 9 **** 【END】除法计算结束 10 11 0 12 13 hello Mufasa
1 class MyMath{ 2 //异常要交给被调用处处理则一定要在方法上使用throw 3 public static int div(int x,int y) throws Exception{ 4 int temp=0; 5 System.out.println("**** 【START】除法计算开始"); 6 try { 7 temp=x/y; 8 }catch (Exception e){ 9 throw e;//向上层抛异常对象 10 }finally { 11 System.out.println("**** 【END】除法计算结束"); 12 } 13 return temp; 14 } 15 } 16 public class Main001 { 17 public static void main(String args[]) { 18 try { 19 System.out.println(MyMath.div(10,0)); 20 } catch (Exception e) { 21 e.printStackTrace(); 22 }finally { 23 System.out.println("hello Mufasa"); 24 } 25 } 26 }
1 **** 【START】除法计算开始 2 **** 【END】除法计算结束 3 java.lang.ArithmeticException: / by zero 4 at MyMath.div(Main001.java:7) 5 at Main001.main(Main001.java:23) 6 hello Mufasa
对于此类操作实际上可以简化,省略掉catch与throw的操作。
try...finally的操作:
1 class MyMath{ 2 //异常要交给被调用处处理则一定要在方法上使用throw 3 public static int div(int x,int y) throws Exception{ 4 int temp=0; 5 System.out.println("**** 【START】除法计算开始"); 6 try { 7 temp=x/y; 8 }finally { 9 System.out.println("**** 【END】除法计算结束"); 10 } 11 return temp; 12 } 13 } 14 15 public class Main001 { 16 public static void main(String args[]) { 17 try { 18 System.out.println(MyMath.div(10,0)); 19 } catch (Exception e) { 20 e.printStackTrace(); 21 }finally { 22 System.out.println("hello Mufasa"); 23 } 24 } 25 }
在以后实际开发过程中,这种异常的处理格式是最为重要的,尤其是当与一些资源进行访问操作的时候尤其重要。
8,RuntimeException
通过之前的分析可以发现只要方法后面带有throws往往都是告诉用户本方法可能产生的异常是什么,所以这个时候可以观察一段代码。
1 public class Main003 { 2 public static void main(String[] args) { 3 int num=Integer.parseInt("123"); 4 } 5 }
1 public static int parseInt(String s) throws NumberFormatException
这个方法明确的抛出了异常,但是在处理的时候并没有强制性要求处理,观察一下NumberFormatException的继承结构,同时也观察数学异常类的继承结构打开Integer类中的parseInt()方法的定义
ArithmeticException() |
NumberFormatException() |
java.lang.Object java.lang.Throwable java.lang.Exception java.lang.RuntimeException java.lang.ArithmeticException |
java.lang.Object java.lang.Throwable java.lang.Exception java.lang.RuntimeException java.lang.IllegalArgumentException java.lang.NumberFormatException |
如果现在所有程序执行上只要使用了throws定义的方法都必须要求开发者进行手工处理,那么这个代码的编写就太麻烦了,所以在设计的过程之中,考虑到代码的编写的方便,所以提供有一个灵活的、可选的异常处理父类“RuntimeException”,这个类的子类可以不需要强制性的处理。
面试题:请解释RuntimeException与Exception的区别?请列举出几个常见的RuntimeException;
①RuntimeException是Exception的子类;
②RuntimeException标注的异常可以不需要进行强制性处理,而Exception需要进行强制性处理;
①常见的RuntimeException:
IndexOutOfBoundsException(范围越界异常)、
NumberFormatException(数据格式异常)、
NullPointerException(空指针异常)
9,自定义异常类
在JDK之中提供有大量的异常类型,但是在实际的开发之中可能这些异常类型未必够你使用,你不可能所有的设计里面都只是抛出Exception,所以这个时候就需要考虑进行自定义异常类。但是对于自定义异常也有两种实现方案:继承Exception或者继承RuntimException
范例:实现自定义异常
RuntimeException
1 class BombException extends RuntimeException{ 2 public BombException(String msg){ 3 super(msg); 4 } 5 } 6 class Food{ 7 public static void eat(int num) throws BombException{ 8 if (num>10){ 9 throw new BombException("吃太多,肚子爆"); 10 }else { 11 System.out.println("正常吃,不怕胖"); 12 } 13 } 14 } 15 public class Main004 { 16 public static void main(String[] args) { 17 Food.eat(11); 18 } 19 }
Exception
1 class BombException extends Exception{ 2 public BombException(String msg){ 3 super(msg); 4 } 5 } 6 class Food{ 7 public static void eat(int num) throws BombException{ 8 if (num>10){ 9 throw new BombException("吃太多,肚子爆"); 10 }else { 11 System.out.println("正常吃,不怕胖"); 12 } 13 } 14 } 15 public class Main004 { 16 public static void main(String[] args) { 17 try { 18 Food.eat(11); 19 }catch (BombException e){ 20 e.printStackTrace(); 21 } 22 23 } 24 }
在以后的项目开发过程中会接触大量的自定义异常处理,如果遇见了你不清楚的异常,最简单的方式就是通过搜索引擎查询一下异常可能产生的原因。
10,assert
从JDK1.4后追加了一个断言的功能,确定代码执行到某行后一定是所期待的结果。在实际开发之中,对于断言而言,并不一定是准确的,也有可能出现偏差,但是这种偏差不应该影响程序的正常执行。
范例:断言的使用
1 public class Main005 { 2 public static void main(String[] args) { 3 int x=10; 4 //中间会出现许多的x变量的操作步骤 5 assert x==100:"x的数值不是100"; 6 } 7 }
(java -ea Main005 |
Exception in thread "main" java.lang.AssertionError: x的数值不是100 at Main005.main(Main005.java:5) |
如果现在要想执行断言,则必须在程序执行的时候加入参数(java -ea Main005)
所以在java里面并没有将断言设置成为一个程序必须执行的步骤,需要特定的环境下可以开启。