(材料源于网络)
JavaSE 6th day
——Exception
1、本次课程知识点
1、认识异常及异常的默认处理操作;
2、异常处理语句的使用;
3、throw和throws关键字的作用;
4、异常处理的标准操作格式;
5、自定义异常和assert关键字的使用。
2、具体内容
2.1 认识异常(理解)
异常是导致程序中断执行的一种指令流,即:程序之中一旦产生了异常之后,程序在默认情况下将中断执行。
范例:观察如下的程序:
public class Demo { public static void main(String args[]) { int x=10; int y=0; System.out.println("========Start of the calculation========"); int result=x/y; System.out.println("Result of the calculation:"+result); System.out.println("========End of the calculation========"); } } |
此时,由于被除数是0,所以现在程序运行之后的结果是:
现在很明显,是在异常出现之后的语句都不再执行了,通过程序可以发现,异常出现之后,程序将导致运行,即中断执行了。
额外:计算机的两大杀手
对于计算机而言,最导致的两个操作:断电、除以0;
2.2 异常处理语法(重点)
如果现在希望发生异常之后,程序依然可以正确的执行完毕,则可以采用如下的异常处理格式完成:
try{ //可能出现异常的语句 }[catch(异常类 异常类对象){ //异常的处理 }[catch(异常类 异常类对象){ //异常的处理 }[catch(异常类 异常类对象){ //异常的处理 }……]]] [finally{ //不管是否有异常都要执行此代码 }] |
范例:使用try……catch处理异常
public class Demo { public static void main(String args[]) { int x=10; int y=0; System.out.println("========Start of the calculation========"); try{ int result=x/y; System.out.println("Result of the calculation:"+result); }catch(ArithmeticException e){ //此处捕获的是算术异常 System.out.println(e); } System.out.println("========End of the calculation========"); } } |
此时程序的输出:
==================Start of the calculation================== java.lang.ArithmeticException: / by zero ==================End of the calculation================== |
通过输出可以发现,现在的程序可以正常的执行完毕了,而且可以发现,在try语句之中,捕获异常之后,异常代码之后的语句将不再执行了,如果现在没有异常发生,则不再执行catch语句中的操作。
现在的异常输出直接采用了输出异常对象的方式完成,而这种异常的信息并不是完整的,所以很多时候都会直接调用异常类中的printStackTrace()方法打印异常信息;
public class Demo { public static void main(String args[]) { int x = 10; int y = 1; System.out.println("========Start of the calculation========"); try { int result = x / y; System.out.println("Result of the calculation:" + result); } catch (ArithmeticException e) {// 此处捕获的是算术异常 ae.printStackTrace();//直接输出对象 }finally{ //异常的出口 System.out.println("Execute the procedure though the Exception!"); } System.out.println("========End of the calculation========"); } } |
========Start of the calculation======== Result of the calculation:10 Execute the procedure though the Exception! ========End of the calculation======== |
但是写到这里也有一个问题了,抛开异常处理程序不谈,可以发现最后的输出语句也是无论是否有异常都会执行,实际上对于finally程序有特定的使用语法,而且是要结合之后的异常处理的标准格式来讲的。
但是随着java的发展,除了以上的两种格式之外,也存在了另外一种格式:
public class Demo { public static void main(String args[]) { int x = 10; int y = 1; System.out.println("========Start of the calculation========"); try { int result = x / y; System.out.println("Result of the calculation:" + result); } finally {// 异常的出口 System.out.println("Execute the procedure though the Exception!"); } System.out.println("========End of the calculation========"); } } |
即在原来的基础上删掉了catch关键字,但这种写法建议绝对不要使用!
在之前的程序之中,已经进行一个异常的处理,如果现在要把程序扩充一下,例如:现在希望两个计算的数字可以由初始化参数指定,所以现在的代码修改如下:
public class Demo { public static void main(String args[]) { int x = 0; int y = 0; System.out.println("========Start of the calculation========"); try { x = Integer.parseInt(args[0]); // 接收第一个数字 y = Integer.parseInt(args[1]); // 接收第二个数字 int result = x / y; System.out.println("Result of the calculation:" + result); } catch (ArithmeticException e) {// 此处捕获的是算术异常 e.printStackTrace();// 直接输出对象 } finally { // 异常的出口 System.out.println("Execute the procedure though the Exception!"); } System.out.println("========End of the calculation========"); } } |
但是此时一些新的问题也来了:
●问题一:如果用户在执行程序的时候没有输入参数:
● 问题二:如果用户输入的参数不是数字:
● 问题三:输入的除数是0:
通过以上的程序分析可以发现,每种异常都应该有每种异常的类型,而之前的程序所有的异常都只按照了一种异常处理,那么这之外的异常就无法处理了,所以此时可以在try语句后增加多个catch。
public class Demo { public static void main(String args[]) { int x = 0; int y = 0; System.out.println("========Start of the calculation========"); try { x = Integer.parseInt(args[0]); // 接收第一个数字 y = Integer.parseInt(args[1]); // 接收第二个数字 int result = x / y; System.out.println("Result of the calculation:" + result); } catch (ArithmeticException e) {// 此处捕获的是算术异常 e.printStackTrace();// 直接输出对象 } catch(ArrayIndexOutOfBoundsException e){ e.printStackTrace(); }catch(NumberFormatException e){ e.printStackTrace(); }finally { // 异常的出口 System.out.println("Execute the procedure though the Exception!"); } System.out.println("========End of the calculation========"); } } |
现在程序之中可以处理三个异常,但是程序写到这个地方一个问题就该出现了,以上所有的异常都是一个个实验出来的,那么并没有感觉到异常处理的方便,如果现在是一段不熟悉的代码,你有可能花那么多的时间进行分析吗?
2.3 异常的处理流程(核心)
如果现在要想简化异常的处理操作,那么就必须首先掌握异常的两大内容:继承结构、处理流程;
以“ArithmeticException”异常类为例,观察此类的继承结构:
可以发现ArithmeticException类的父类是Throwable,表示允许抛出的,在Throwable中有两个子类:
● Error:表示的是JVM出错,即:程序还没有运行时所发生的错误,用户无法处理;
● Exception:表示程序运行中发生的错误,用户可以处理;
一般情况下,所谓的异常处理异常指的是都是Exception的子类,那么按照之前所讲解的对象的转型操作而言,所有的子类对象都可以向父类自动转型,所以,那么如果现在要想简化异常的处理操作,则还要进行异常的处理流程分析。
1、在程序之中如果发生异常,则首先会有JVM自动的产生一个指定异常类的实例化对象;
2、此异常对象要被try语句所捕获,如果现在没有异常处理语句,则也交给JVM处理;
3、将此异常类的对象想方法参数传递那样,与每一个catch进行匹配,如果匹配成功,则使用catch进行处理,如果匹配不成功则向下继续匹配,如果都没有成功的则交给JVM采用默认的处理方式(程序中断);
4、当程序之中异常处理完毕之后,都会调用finally程序进行异常的出口收尾工作;
5、如果之后有其他语句,则继续执行;
通过以上的分析可以得出:所谓的异常出来实际上还是一个引用数据类型的操作流程,
所以按照子类对象自动向父类对象转型的技术要求,则可以直接使用Exception(异常的父类)进行全部异常的处理。
如果现在希望省事,可以利用Exception进行处理,但是由于Exception的捕获范围更大,所以这种捕获必须放在捕获范围小的异常之后,否则程序在编译时就会出现错误提示。
public class Demo { public static void main(String args[]) { int x = 0; int y = 0; System.out.println("========Start of the calculation========"); try { x = Integer.parseInt(args[0]); // 接收第一个数字 y = Integer.parseInt(args[1]); // 接收第二个数字 int result = x / y; System.out.println("Result of the calculation:" + result); } catch (ArithmeticException e) {// 此处捕获的是算术异常 e.printStackTrace();// 直接输出对象 } catch (ArrayIndexOutOfBoundsException e) { e.printStackTrace(); } catch (NumberFormatException e) { e.printStackTrace(); }catch (Exception e) { e.printStackTrace(); } finally {// 异常的出口 System.out.println("Execute the procedure though the Exception!"); } System.out.println("========End of the calculation========"); } } |
但是,Exception肯定把所有的异常都包含了,所以现在实际上以上的程序,可以采用更简便的做法:
public class Demo { public static void main(String args[]) { int x = 0; int y = 0; System.out.println("========Start of the calculation========"); try { x = Integer.parseInt(args[0]); // 接收第一个数字 y = Integer.parseInt(args[1]); // 接收第二个数字 int result = x / y; System.out.println("Result of the calculation:" + result); } catch (Exception e) { e.printStackTrace(); } finally { // 异常的出口 System.out.println("Execute the procedure though the Exception!"); } System.out.println("========End of the calculation========"); } } |
可是这种做法只适合于异常处理要求不高的开发环境下,如果现在开发之中要求每种异常都要分别进行处理的话,这种做法就不可取了,必须编写多个catch语句。
面试题:
1、请解释Error和Exception的求别:
● Error:表示的是JVM出错,即:程序还没有运行时所发生的错误,用户无法处理;
● Exception:表示程序运行中发生的错误,用户可以处理;
2、请解释异常处理的流程:
A、在程序之中如果发生异常,则首先会有JVM自动的产生一个指定异常类的实例化对象;
B、此异常对象要被try语句所捕获,如果现在没有异常处理语句,则也交给JVM处理;
C、将此异常类的对象想方法参数传递那样,与每一个catch进行匹配,如果匹配成功,则使用catch进行处理,如果匹配不成功则向下继续匹配,如果都没有成功的则交给JVM采用默认的处理方式(程序中断);
D、当程序之中异常处理完毕之后,都会调用finally程序进行异常的出口收尾工作;
E、如果之后有其他语句,则继续执行;
2.4 throws关键字(重点)
throws关键字的主要功能是在方法定义上使用的,表示一个方法之中不处理异常,而交给程序的被调用处处理,实际上就属于一种问题的转移,即:原本是A的问题现在交给了B处理。
package course_2;
class MyMath { public static int div(int x,int y) throws Exception {//不再由方法处理 return x / y; } }
public class Demo { public static void main(String args[]) { try { System.out.println(MyMath.div(10, 0)); } catch (Exception e) { e.printStackTrace(); } } } |
java.lang.ArithmeticException: / by zero at course_2.MyMath.div(Demo.java:6) at course_2.Demo.main(Demo.java:13) |
实际上现在在主方法之中,也可以继续向上抛出异常,直接写上throws即可。
package course_2;
class MyMath { public static int div(int x,int y) throws Exception {//不再由方法处理 return x / y; } }
public class Demo { public static void main(String args[])throws Exception { MyMath m = new MyMath(); System.out.println(m.div(10, 0)); } } |
java.lang.ArithmeticException: / by zero at course_2.MyMath.div(Demo.java:5) at course_2.Demo.main(Demo.java:12) |
主方法之上就是JVM了,所以如果在主方法上写了throws,则表示交给JVM进行处理(所以在程序主方法中就可以不写try……catch语句进行异常的捕获和处理了),而JVM的处理原则就是程序的中断执行,所以在开发之中,不要在main()方法上继续向上抛异常了。
2.5 throw关键字(重点)
在之前的throws是在方法上定义的,而且在方法上抛出的异常也有可能就是由JVM自动抛出的,但是现在可以利用throw关键字手工的抛出一个异常。
package course_2;
publicclass Demo { public static void main(String args[])throws Exception { try { throw new Exception("抛着玩。"); } catch (Exception e) { e.printStackTrace(); } } } |
java.lang.Exception:抛着玩。 at course_2.Demo.main(Demo.java:6) |
不理解,在开发之中躲异常还来不及呢,还往上招惹,不懂。请继续看一下范例。
范例:在方法中抛出异常。
public class Demo { public void throwException() throws Exception{ System.out.println("throwException() strart."); throw new Exception("Evil exception!"); // System.out.println(); //注意在抛出异常后是不能继续再写其他语句的。 }
public static void main(String args[]) { System.out.println("MAIN strat."); try { new Demo().throwException(); } catch (Exception e) { System.out.println("Receipted EXCEPTION!"); e.printStackTrace(); } System.out.println("MAIN end."); } } |
MAIN strat. throwException() strart. Receipted EXCEPTION! java.lang.Exception: Evil exception! at firstCourse.Demo.throwException(Demo.java:6) at firstCourse.Demo.main(Demo.java:13) MAIN end. |
笔记:和在main方法中抛出异常不同的是,在非main方法中抛出异常的话通常在这个方法中都会使用throws Exception把所长生的异常交给调用处处理,否则自定义的throw异常就没什么意义了,如“抛着完”这个例子。
面试题:请解释throw和throws的区别?
● throw表示人为的进行异常的抛出,手工抛出对象;
● throws是用于方法的声明上,表示一个方法不处理异常,而交给被调用处处理;
2.6异常处理的标准格式(最核心)
现在学习完了:try……catch……finally、throw、throws等五个关键字,那么这五个关键字改如何应用呢?下面通过一个程序的分析完成。
例如,现在要求定义一个div()方法,此方法要求在计算开始和结束的时候有信息输出,而且如果出现了异常,则要求交给被调用处处理。
package course_2;
class MyMath { public static int div(int x,int y) throws Exception {//不再由方法处理。此处不加结果一样,因为本方法中已使用try……catch语句 System.out.println("*********计算开始*********"); int result = 0; try { result = x / y; } catch (Exception e) { System.out.println(e);//如果把异常向上抛(throw e),则一般不再写此语句,此处为了测试所需 throw e; // 把异常向上抛 // 以上两条语句不能互换位置,否则在编译时就出错 } finally { System.out.println("*********计算结束*********"); } return result; // 如果没有异常产生或“throw e”语句,此行代码都将执行! //没有“throw e”情况下,会在div()方法处理完异常后, //将0返回到被调用处 } }
public class Demo { public static void main(String args[]) { try { System.out.println(MyMath.div(10, 0)); } catch (Exception e) { System.out.println("主方法处理异常前"); e.printStackTrace(); System.out.println("主方法处理异常后"); } } } |
*********计算开始********* java.lang.ArithmeticException: / by zero *********计算结束********* 主方法处理异常前 java.lang.ArithmeticException: / by zero at course_2.MyMath.div(Demo.java:8) at course_2.Demo.main(Demo.java:25) 主方法处理异常后 |
从程序中可以发现,通过合理的搭配程序完成了需要的功能,不过在MyMath类中div()方法是否会产生异常都会执行“计算结束”的语句。如果有异常产生则将异常交给调用处处理。本程序的具体执行流程如下图:
以上只是演示了一个基本的程序模型,而在日后的操作之中,可以把两个输出语句换成某些资源的打开和关闭操作。
注意:finally语句块的编写要求。
finally作为异常的统一出口,所以在此语句块的编写中尽可能不要出现像throw或return这样的语句,这样可以避免不必要的问题出现。
2.7 断言:assert(了解)
所谓的断言指的就是肯定某一个结果的返回值是正确的,如果最终此结果的返回值是错误的,则通过断言肯定会提示错误信息。
断言的定义格式:
assert boolean表达式; assert boolean表达式:详细的信息; |
如果以上的boolean表达式的结果为true,则什么错误信息都不会提示;如果为false,则会提示错误信息;如果没有声明详细的描述,则系统会使用默认的错误信息提示方式。
public class Demo { public static void main(String args[]) { int x = 10; assert x == 20; //断言现在的x是20 System.out.println(x); } } |
10 |
在正常情况下,断言是不会起左右的,而如果要想启动断言,则必须进行参数的配置:java -ea Demo
此时表示程序要进行断言的检查,那么默认情况下,断言的错误信息是由系统提供的,当然也可以由用户自己指定:
public class Demo { public static void main(String args[]) { int x = 10; assert x == 20:"x的内容不是20";//断言现在的x是20 System.out.println(x); } } |
断言是在JDK1.4之后增加的,在开发之中也不会使用。
2.8 自定义异常类(了解)
在java之中已经为用户提供了大量的异常类的信息,但是很多时候用户往往希望可以定义一些异常,表示自己项目中所使用的异常类,那么在这种情况下就可以让一个类直接继承Exception类即可实现。
class MyException extends Exception{ public MyException(String msg){ super(msg); } }
public class Demo{ public static void main(String args[])throws Exception{ throw new MyException("自定义异常类。"); } } |
一般的开发之中,很少会用到此概念,但是做一些系统的架构设计的时候还是会用到的,例如以后的框架之中会有许多新的异常类型,实际上所有的异常类最简单的处理方式都是使用Exception。
2.9 RuntimeException(重点)
下面首先来观察如下一段程序:
public class Demo { public static void main(String args[]) { int num = Integer.parseInt("100");// 字符串变为int System.out.println(num); } } |
100 |
现在的程序编译上没有任何的错误,但是下面观察一下parseInt()方法的定义:
public static int parseInt(String s)throws NumberFormatException |
这个方法上使用了一个throws关键字抛出了异常,那么为什么程序中不去处理呢?
下面首先观察一下此异常的继承结构:
可以发现这个异常类是RuntimeException的子类,而在java之中,所有的RuntimeException的异常类可以根据用户的需要进行选择性的处理,不处理也不会有问题,但是一旦出错,则会由JVM默认处理。
面试题:请解释Exception和RuntimeException的区别,并列出几个常见的RuntimeException。
● Exception和RuntimeException的区别:Exception的异常必须处理,而RuntimeException的异常可以选择性处理;
● 常见的RuntimeException:NumberFormatException、ArithmeticException、NullPointerException、ClassCastException、ArrayIndexOutOfBoundsException;
3、总结
1、明确的理解异常处理的作用:保证程序的正常执行完毕;
2、异常处理的流程;
3、异常处理的标准格式:try……catch……finally、throws、throw的联合应用。