目标
- 了解异常的产生原理
- 掌握异常处理语句的基本格式
- 掌握throw和throws关键字的作用
- 可以自定义异常
- 了解Exception和RuntimeException的区别
- 了解断言assert的作用
7.1 异常的基本概念
异常时导致程序中断运行的一种指令流,如果不对异常进行正确的处理,则可能导致程序的中断执行,造成不必要的损失,所以在程序的设计中 必须要考虑到各种异常的产生,并正确地做好相应的处理,这样才能保证程序正常地执行。在java中一切的异常都秉承着面向对象的设计思想。所有的异常都是以类和对象的形式存在,除了java中已经提供的各种异常类之外,用户也可以根据需要定义自己的异常类。
7.1.1 为什么需要异常处理
首先看下面一个例子:
public class ExceptionDemo01{
public static void main(String args[]){
System.out.println("*****计算开始*****");
int i = 10;
int j = 0;
int temp = i / j;
System.out.println("两数相除结果为: " + temp);
System.out.println("*****计算结束*****");
}
}
以上程序没有对异常进行处理。会编译成功,运行结果如下:
-------------------------------------------------
*****计算开始*****
Exception in thread "main" java.lang.ArithmeticException: / by zero
at ExceptionDemo01.main(Unknown Source)
执行错误
-------------------------------------------------
由于程序没有对异常进行处理,运行时一旦出现了异常后,程序会立刻退出。 后面的语句都不会执行。为了不影响程序的正常运行,则需要对异常进行处理。
7.1.2 在程序中使用异常处理
I, 在java中用try ... catch...或者try ... catch... finally...语句来对异常进行处理操作。 格式如下
try{
//有可能出现异常的语句块
}catch(异常类 异常对象){
//异常的处理语句块
}[catch(异常类 异常对象){
//异常的处理语句块
}catch(异常类 异常对象){
//异常的处理语句块
}...]
[finally]{
//一定会运行的程序代码块
}
如果在try中产生了异常,则程序会自动跳转到catch语句中找到匹配的异常类型进行相应的处理。最后不管程序是否会产生异常,或者是执行完异常处理后,都会执行到finally块。 finally块作为异常的统一出口,始终会被执行的。
finally块是可以省略的,如果省略了finally块,则在catch()块运行结束后,程序会跳转到try - catch 块之后继续进行。
public class ExceptionDemo02{
public static void main(String args[]){
System.out.println("*****计算开始*****");
int i = 10;
int j = 0;
try{
int temp = i / j;
System.out.println("两数相除结果为: " + temp);
System.out.println("======================");
}catch(ArithmeticException e){
System.out.println("出现异常" + e + "了");
}
System.out.println("*****计算结束*****");
}
}
运行结果: catch块处理了该异常,catch块执行完后,程序会继续向下执行。
-------------------------------------------------
*****计算开始*****
出现异常java.lang.ArithmeticException: / by zero了
<span style="color:#ff0000;">*****计算结束*****</span>
-------------------------------------------------
II, 有finally块的异常处理
public class ExceptionDemo03{
public static void main(String args[]){
System.out.println("*****计算开始*****");
int i = 10;
int j = 0;
try{
int temp = i / j;
System.out.println("两个数相除的结果是: " + temp);
System.out.println("=============");
}<span style="color:#ff0000;">catch(ArithmeticException e){
System.out.println("出现异常 -->" + e + "了!!!");
}</span><span style="color:#6600cc;">finally{
System.out.println("this part will be always executed as the way out of block \'try...catch\'");
}</span>
<span style="color:#006600;">System.out.println("*****计算结束*****");</span>
}
}
其运行结果是:
-------------------------------------------------
*****计算开始*****
<span style="color:#ff0000;">出现异常 -->java.lang.ArithmeticException: / by zero了!!!</span>
<span style="color:#6600cc;">this part will be always executed as the way out of block 'try...catch'</span>
<span style="color:#006600;">*****计算结束*****</span>
-------------------------------------------------
III, 使用多个catch语句块来捕捉和处理多个异常(多个异常的可能性), 如一下代码ExceptionDemo04.java在用户输入不同的格式下回产生不同的异常,但是在该程序中我们只处理了当被除数为0的时会产生的ArithmeticException异常。 对于其他的异常我们都没有进行处理我们在程序中必须对可能产生的异常情况都进行捕获并处理。
public class ExceptionDemo04{
public static void main(String args[]){
System.out.println("*****计算开始*****");
int i = 0;
int j = 0;
try{
String str1 = args[0]; //<span style="color:#ff0000;">若用户没有输入参数,或者输入参数少于两个,则会产生ArrayIndexOutOfBoundsException</span>
String str2 = args[1];
i = Integer.parseInt(str1); //<span style="color:#ff0000;">若str1, str2不是纯数字的字符串,会产生NumberFormatException</span>
j = Integer.parseInt(str2);
int temp = i / j; //<span style="color:#ff0000;">若被除数为0, 则会产生ArithmeticException异常</span>
System.out.println("两个数相除的结果是: " + temp);
System.out.println("=============");
}catch(<span style="color:#ff0000;">ArithmeticException</span> e){
System.out.println("捕获到异常" + e + "了");
}finally{
System.out.println("<span style="color:#6600cc;">this part will be always executed as the way out of block \"try...catch\"</span>");
}
}
}
我们正确地代码应该对程序中可能会产生的异常都进行捕获及处理。但是当我们不知道具体的Exception类型时,可以使用它们公共的父类Exception。so, 以上代码修改为:
public class ExceptionDemo05{
public static void main(String args[]){
int i = 0;
int j = 0;
try{
String str1 = args[0]; //<span style="color:#ff0000;">若用户没有输入参数,或者输入参数少于两个,则会产生ArrayIndexOutOfBoundsException</span>
String str2 = args[1];
i = Integer.parseInt(str1); //<span style="color:#ff0000;">若str1, str2不是纯数字的字符串,会产生NumberFormatException</span>
j = Integer.parseInt(str2);
int temp = i / j; //<span style="color:#ff0000;">若被除数为0, 则会产生ArithmeticException异常</span>
System.out.println("两个数相除的结果是: " + temp);
System.out.println("==========");
}catch(ArithmeticException e){
System.out.println("捕获除数为0的异常" + e);
}catch(NumberFormatException e){
System.out.println("捕获到用户输入的不是纯数字的字符串异常" + e);
}catch(ArrayIndexOutOfBoundsException e){
System.out.println("捕获到用户输入参数不正确的一场" + e);
}
System.out.println("<span style="color:#6600cc;">this part will be always executed as the way out of block \"try...catch\"</span>");
}
}
7.1.3 异常类的继承结构
Java的异常结构中,有两个最常用的类,分别是Exception和Error。 他们都是Throwable的子类!!
I, Exception 一般表示程序中出现的问题,可以直接在程序中使用try...catch处理
II, Error 一般指的是JVM错误,在程序中无法处理。
III, 异常信息的输出方式, 在catch语句输出异常信息时,除了可以直接使用“System.out.println(异常对象);”的方式打印异常信息外,有时也会直接使用Exception类中的printStackTrace()方法输出异常信息,代码如下:
e.printStackTrace(); //<span style="color:#990000;">使用此方式打印出的异常信息是最完整的!!!</span>
7.1.4 java的异常处理机制
在java的异常处理中,实际上也是按照面对对象的方式进行处理的。 处理的步骤如下
I, 程序一旦产生异常,则首先会产生一个异常类的实例化对象。
II, 在try语句中对此异常对象进行捕捉。
III, 产生的异常对象与catch语句中的各个异常类型进行匹配,如果匹配成功,则执行catch语句中的程序块。
在程序中,如果我们知道可能会产生的异常类型的话,则必须进行捕获该异常的具体类型; 当我们无法知道异常的类型时,利用对象的多态性(所有子类的实例化对象都可以使用父类来接收), 通过程序自动完成的向上转型功能,我们使用Exception来接收所有的异常对象。
但是,所有捕获范围小的异常必须放在捕获范围大的异常之前!!否则程序编译时就会出错。
*****当我们无法预知程序会产生的异常类型时,可以在单步调试过程中观察错误信息,一般都可以得到异常的具体类型,然后对应修改我们的程序代码*******
7.2 throws与throw关键字
7.2.1 throws关键字
在定义一个方法时可以使用throws关键字声明,使用throws声明的方法表示此方法不处理异常,而是交给方法的调用处进行处理,throws使用格式如下:
public 返回值类型 方法名称(参数列表) throws 异常类{}
当调用一个throws声明的方法时,不管该方法有没有产生异常,在方法的调用处都必须进行异常处理,即都要使用try...catch块进行异常的捕获及处理。
class _Math{
public int div(int i, int j) throws Exception{ //本方法中不处理异常
int temp = i / j; //此处可能产生ArithmeticException异常
return temp;
}
}
上面代码块可能会产生ArithmeticException异常,但是由于方法div()使用了throws关键字,表示不管有没有异常,该方法内部不处理异常,必须在调用该方法处进行异常处理。代码如下:
class _Math{
public int div(int i, int j){
int temp = i / j;
return temp;
}
}
public class ThrowsDemo01{
public static void main(String args[]){
_Math m = new _Math();
try{
System.out.println("除法操作" + m.div(10, 0));
}catch(Exception e ){ //捕捉所有异常情况
e.printStackTrace();
}
}
}
当主方法也使用了throws关键字时,此时主方法向上抛出异常,由于主方法是程序的入口,所以由主方法抛出的异常只能抛给JVM进行处理。
class _Math{
public int div(int i, intj) throws Exception{
int temp = i / j;
return temp;
}
}
public class ThrowsDemo02{
//本方法中的所有异常都可以不使用try...catch处理。
public static void main(String args[]) throws Exception{
_Math m = new _Math();
System.out.println("除法操作: " +m.div(10, 0));
}
}
7.2.2 throw关键字
throw,用来直接抛出一个异常类的实例化对象。throw 一般情况下不会单独使用,程序员会抛出程序中已经产生的异常类实例对象。
public class ThrowDemo01{
public static void main(String args[]){
try{
throw new Exception("人为手动抛出的异常"); //抛出一个异常的实例化对象,new Exception(message),调用的是Exception类的其中一个构造方法。
}catch(Exception e){
e.printStackTrace();
}
}
}
运行结果:
-------------------------------------------------
java.lang.Exception: 人为手动抛出的异常
at ThrowDemo01.main(Unknown Source)
-------------------------------------------------
7.2.3 范例 -- throw与throws的应用
实际开发中,一般把try...catch...finally、throw、throws联合起来使用。 例如要求设计相除方法,但是要求必须打印"计算开始"、"计算结束"信息;如果有异常则交给被调用处处理。
class _Math{
public int div(int i, int j) throws Exception{
System.out.println("*****计算开始*****");
int temp = 0;
try{
temp = i / j;
}catch(Exception e){
throw e; //抛出异常,给调用此方法处处理该异常
}finally{
System.out.println("*****计算结束*****"); //不管是否有异常产生,始终都要执行此代码段
}
return temp;
}
}
public class ThrowDemo02{
public static void main(String args[]){
_Math m = new _Math();
try{
System.out.println("除法操作" + m.div(10, 0));
}catch(Exception e){ //进行异常捕获及处理异常
System.out.println("产生了异常。");
e.printStackTrace();
}
}
}
运行结果如下:
-------------------------------------------------
*****计算开始*****
*****计算结束*****
产生了异常。
java.lang.ArithmeticException: / by zero
at <span style="color:#cc0000;">_Math.div(Unknown Source) //异常抛出的准确位置</span>
at <span style="color:#6600cc;">ThrowDemo02.main(Unknown Source) //异常可能是被调用的方法中产生的,此处表示方法的被调用处!!!</span>
-------------------------------------------------
注意观察异常的输出信息,e.printStackTrace(); 输出异常信息,两个at 表示异常的产生处!顺序也表示了方法的调用顺序!!
7.3 Exception类与RuntimeException类
Exception类、RuntimeException类的区别, 请看以下的代码段
public class RuntimeExceptionDemo01{
public static void main(String args[]){
String str ="123"; //定一个由数字组成的字符串
int temp = Integer.parseInt(str); //将字符串变为int整型数据类型
System.out.println(temp * temp);
}
}
代码分析: int temp = Integer.parseInt(str); 因为Integer首字母是大写,表示Integer是一个类,而parseInt()方法可以直接由类名称调用,则表示parseInt()是一个静态方法。此方法的定义如下
public static int parseInt(String s) throws NumberFormatException;
以上方法在声明处使用了throws关键字,但是在方法调用时并没有使用try...catch...进行异常处理。看看NumberFormatException类的继承关系
Exception
|
|-----RuntimeException
|
|-----IllegalArgumentException
|
|------NumberFormatException
由上可以看出NumberFormatException是属于RuntimeException的子类,那么此时就可以知道一下概念
- Exception类的异常在程序中必须使用try...catch进行处理
- RuntimeException类的异常可以不使用try...catch进行处理,但是如果此时有此类异常产生时,则该类异常将由JVM进行处理。
- 虽然RuntimeException类的子类异常可以不用try...catch进行处理。但是一旦出现此类异常,则肯定会导致程序中断执行,所以,为了保证程序在出错后仍然可以继续执行,在开发的时候,最好也使用try...catch来处理此类异常。
7.4 自定义异常类
用户也可以根据自己的需要定义自己的异常类,定义异常类只需要继承Exception类即可。
class MyException extends Exception{
public MyException(String msg){ //构造方法接收异常信息
super(msg); //调用父类Exception中的构造方法
}
}
public class DefaultException{
public static void main(String args[]){
try{
throw new MyException("custom Exception!!!");
}catch(Exception e){
System.out.println(e);
}
}
}
运行结果
-------------------------------------------------
MyException: My own Exception!!!
-------------------------------------------------
7.5 断言assert
断言,就是肯定某一个结果的返回值是正确的。 如果最终此结果的返回值是错误的,则通过断言检查肯定会提示出错误信息。定义格式如下
assert boolean 表达式;
assert boolean 表达式: 详细的信息
如果以上boolean表达式的结果为true,则什么错误信息都不会提示;如果为false,则会提示错误信息;如果没有声明详细信息的描述,系统会使用默认的错误信息提示方式。
public class AssertDemo01{
public static void main(String args[]){
int x[] = {1, 2, 3}; //定义长度为3的整数数组
assert x.length == 0; //断言数组x的长度为0
}
}
虽然上面代码中断言assert是错误的,但是运行该代码时并不会得到任何结果,这是因为java设计assert关键字时,考虑到系统的应用,为了防止某些用户使用assert作为关键字,所以在程序正常运行时断言并不会起任何作用。
如果想要断言assert起作用,则在使用java运行时程序时加入以下参数 -enableassertions(简写为 -ea)
java xxxx -enableassertions
java xxxx -ea
此时再运行 java AssertDemo01 -ea 时会得到以下错误
<span style="color:#cc0000;">Exception in thread "main" java.lang.AssertionError
at AssertDemo01.main(AssertDemo01.java: 4)</span>
需要注意的是
- 虽然断言返回的是boolean值,但是并不能将其作为条件判断语句。
- 断言虽然有检查运行结果的功能,但是一般在开发中并不提倡使用断言。
7.6 本章要点
- 异常是导致程序中断运行的一种指令流,当异常发生时,如果没有进行良好的处理,则程序将会中断执行。
- 异常可以使用 try...catch进行处理,也可以使用try...catch...finally进行处理,在try语句中捕捉异常,然后在catch中处理异常,finally作为异常的统一出口,不管是否有异常产生都要执行此段代码块。
- 异常的最大父类是Throwable,其分为两个子类Excpetion类、Error类。 其中Exception类表示程序处理的异常,而Error类表示JVM错误,一般不由开发人员处理。
- 发生异常后,JVM会自动产生一个异常的实例化对象,并匹配相应的catch语句中的异常类型,也可以利用对象多态性中的向上转型直接捕获Exception。
- throws用在方法声明处,表示该方法不处理异常。
- throw表示在方法中人为手动的抛出一个异常。
- 自定义异常类时,只需要继承Exception类即可。
- 断言是jdk1.4后提供的新功能,可以用来检测程序的执行结果,但是开发中并不提倡使用断言进行检测。