第十章.异常处理
Posted lanshanxiao
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第十章.异常处理相关的知识,希望对你有一定的参考价值。
Java的异常机制主要依赖于try、catch、finally、throw和throws五个关键字。
Java将异常分为两种,Checked异常和Runtime异常,Java认为Checked异常都是可以在运行期间得到解决的异常,所以它强制要求程序处理所有的Checked异常;而Runtime异常则无须处理。
异常处理机制:
Java异常处理机制可以让程序具有极好的容错性,让程序更加健壮。当程序运行出现意外情形时,系统会自动生成一个Exception对象来通知程序,从而实现将“业务功能实现代码”
和“错误处理代码”分离,提供更好的可读性。
使用try...catch捕获异常:
1 //Java异常处理机制的语法结构 2 try{ 3 //业务实现 4 ... 5 }catch (Exception e){ 6 alert 输入不合法 7 goto retry 8 }
若执行try块中的业务逻辑代码时出现异常,系统自动生成一个异常对象,该异常对象提交给Java运行时环境,这个过程被称为抛出(throw)异常。
当Java运行时环境收到异常对象时,会寻找能处理该异常对象的catch块,若找到合适的catch块,则把该异常对象交给该catch块处理,这个过程被称为捕获(catch)异常;
若Java运行时环境找不到捕获异常的catch块,则运行时环境终止,Java程序也将退出。
1 try{ 2 //将用户输入的字符串以逗号(,)作为分隔符,分割成2个字符串 3 String[] posStrArr = inputStr.split(","); 4 //将2个字符串转换成用户下棋的坐标 5 int xPos = Integer.parseInt(posStrArr[0]); 6 int yPos = Integer.parseInt(posStrArr[1]); 7 //把对应的数组元素赋为“O” 8 if(!gb.board[xPos - 1][yPos - 1].equals("+")){ 9 System.out.println("您输入的坐标点已有棋子了," + "请重新输入"); 10 continue; 11 } 12 gb.board[yPos - 1][xPos - 1] = "O"; 13 }catch (Exception e){ 14 System.out.println("您输入的坐标不合法,请重新输入," + "下棋坐标应以x,y的格式"); 15 continue; 16 }
上面代码把处理用户输入字符串的代码都放在try块里进行,只要用户输入的字符串不是有效坐标值(包括字母不能正确解析,没有逗号不能正确解析,解析出来的坐标引
起数组越界...),系统都将抛出异常对象,并把这个异常交个对应的catch块。
异常类的继承体系:
当Java运行时环境接收到异常对象时,如何为该异常对象寻找catch块?上面程序中catch关键字的形式:(Exception e),这意味着每个catch块都是专门用于处理该异
常类及其子类的异常实例。
当Java运行时环境接收到异常对象后,会依次判断该异常对象是否是catch块后异常类或其子类的实例,若是,Java运行时环境将调用该catch块来处理该异常;否则再次
拿该异常对象和下一个catch块里的异常类进行比较。
try块后面的花括号{ }不可省略,即使try块中只有一条代码,也不可省略这个花括号。catch块后的花括号也不可被省略。在try块中声明的变量是代码块内局部变量,只在try
块内有效,在catch块中不能访问该变量。
Java常见异常类之间的继承关系图:
从图中可看出,Java把所有的非正常情况分成两种:异常(Exception)和错误(Error),它们都继承Throwable父类。
Error错误一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,动态链接失败等,这种错误无法恢复或不可捕获,将导致应用程序中断。通常应用程序无法处理这些错误,
因此应用程序不应该试图使用catch块来捕获Error对象,也无需在其throws子句中声明该方法可能抛出Error及其任何子类。
1 public class DivTest{ 2 public static void main(String[] args){ 3 try{ 4 int a = Integer.parseInt(args[0]); 5 int b = Integer.parseInt(args[1]); 6 int c = a / b; 7 System.out.println("您输入的两个数相除的结果是:" + c); 8 }catch(IndexOutOfBoundsException ie){ 9 System.out.println("数组越界:运行程序时输入的参数个数不够"); 10 }catch(NumberFormatException ne){ 11 System.out.println("数字格式异常:程序只能接收整数参数"); 12 }catch(ArithmeticException ae){ 13 System.out.println("算术异常"); 14 }catch(Exception e){ 15 System.out.println("未知异常"); 16 } 17 } 18 }
IndexOutOfBoundsException:索引越界异常
NumberFormatException:数字格式异常
ArihmeticException:算术异常,计算的表达式无意义
1 import java.util.Date; 2 3 public class NullTest{ 4 public static void main(String[] args){ 5 Date d = null; 6 try{ 7 System.out.println(d.after(new Date())); 8 }catch(NullPointerException ne){ 9 System.out.println("空指针异常"); 10 }catch(Exception e){ 11 System.out.println("未知异常"); 12 } 13 } 14 }
NullPointerException:当试图调用一个null对象的实例方法或实例变量时,就会引发NullPointerException异常
捕获异常时注意,一定先捕获小异常,再捕获大异常。
Java7 提供的多异常捕获:
Java7以前,每个catch块只能捕获一种类型的异常;但从Java7开始,一个catch块可以捕获多种类型的异常。
使用一个catch块捕获多种类型的异常时需要注意如下两个地方:
1.捕获多种类型的异常时,多种异常类型之间需要用(|)竖线隔开
2.捕获多种类型的异常时,异常变量有隐式的final修饰,因此程序不能对异常变量重新赋值。
1 public class MultiExceptionTest{ 2 public static void main(String[] args){ 3 try{ 4 int a = Integer.parseInt(args[0]); 5 int b = Integer.parseInt(args[1]); 6 int c = a / b; 7 System.out.println("您输入的两个数相除的结果是:" + c); 8 }catch(IndexOutOfBoundsException | NumberFormatException | ArithmeticException ie){ 9 System.out.println("程序放生了索引越界,数字格式异常、算术异常之一"); 10 //捕获多异常时,异常变量默认有final修饰 11 //所以下面代码有错 12 ie = new ArithmeticException("test"); 13 }catch(Exception e){ 14 System.out.println("未知异常"); 15 //捕获一种类型的异常时,异常变量没有final修饰 16 //所以下面代码完全正确 17 e = new RuntimeException("test"); 18 } 19 } 20 }
捕获多种类型异常时,异常变量使用隐式的final修饰,因此产生编译错误;捕获一种异常时,异常变量没有final修饰,因此不会出错。
访问异常信息:
若程序需要在catch块中访问异常对象的相关信息,可通过访问catch块后面的异常形参来获得。
当Java运行时决定调用某个catch块来处理该异常对象时,会将异常对象赋给catch块后的异常参数,程序即可通过该参数来获得异常的相关信息:
1.getMessage():返回该异常的详细描述字符串
2.printStackTrace():将该异常的跟踪栈信息输出到标准错误输出
3.printStackTrace(PrintStream s):将该异常的跟踪栈信息输出到指定输出流
4.getStackTrace():返回该异常的跟踪栈信息
1 import java.io.FileInputStream; 2 import java.io.IOException; 3 4 public class AccessExceptionMsg{ 5 public static void main(String[] args){ 6 try{ 7 FileInputStream fis = new FileInputStream("a.txt"); 8 }catch(IOException ioe){ 9 System.out.println(ioe.getMessage()); 10 ioe.printStackTrace(); 11 } 12 } 13 }
“a.txt”(系统找不到指定的文件):是调用异常的getMessage()方法返回的字符串。下面更详细的信息是该异常的跟踪栈信息。
使用finally回收资源:
有些时候,程序在try块中打开了一些物理资源(如:数据库连接,网络连接,磁盘文件等),这些物理资源都必须显示回收。
Java的垃圾回收机制不会回收任何物理资源,垃圾回收机制只能回收堆内存中对象所占用的内存。
为保证一定能回收try块中打开的物理资源,异常处理机制提供了finally块。不管try块中的代码是否出现异常,也不管哪一个catch块被执行,甚至在try块或catch块中执行了
return语句,finally块总是被执行。完整的Java异常处理语法结构如下:
1 //完整的Java异常处理语法结构 2 try{ 3 //业务实现 4 ... 5 }catch (SubException e){ 6 //异常处理块1 7 ... 8 }catch(SubException2 e){ 9 //异常处理块2 10 ... 11 } 12 ... 13 finally{ 14 //资源回收 15 ... 16 }
异常处理语法结构中只有try块是必须的,即若没有try块,则不能有后面的catch块和finally块;
catch块和finally块都是可选的,但catch块和finally块至少出现其中之一,也可以同时出现;
可以有多个catch块,捕获父类异常的catch块必须位于捕获子类异常的后面;
但不能只有try块,既没有catch块,也没有finally块;
多个catch块必须位于try块之后,finally块必须位于所有的catch块之后。
1 import java.io.FileInputStream; 2 import java.io.IOException; 3 4 public class FinallyTest{ 5 public static void main(String[] args){ 6 FileInputStream fis = null; 7 try{ 8 fis = new FileInputStream("a.text"); 9 }catch(IOException ioe){ 10 System.out.println(ioe.getMessage()); 11 //return语句强制方法返回 12 return ; 13 //使用exit退出虚拟机 14 //System.exit(1); 15 }finally{ 16 //关闭磁盘文件,回收资源 17 if(fis != null){ 18 try{ 19 fis.close(); 20 }catch(IOException ioe){ 21 ioe.printStackTrace(); 22 } 23 } 24 System.out.println("执行finally块里的资源回收!"); 25 } 26 } 27 }
finally块被执行了,将上面代码的return ;注释掉,将System.exit(1);打开:
上面结果表明finally块没有被执行。若在异常处理代码中使用System.exit(1)语句退出虚拟机,则finally块将失去执行的机会。
除非在try块、catch块中调用了退出虚拟机的方法,否则不管在try块,catch块中执行了怎样的代码,出现怎样的情况,异常处理的finally块总会被执行。
通常情况下,不要再finally块中使用return或throw等导致方法终止的语句,(throws语句将在后面介绍),一旦在finally块中使用了return或throw语句,将会导致try块,
catch块中的return、throw语句失效:
1 public class FinallyFlowTest{ 2 public static void main(String[] args) throws Exception { 3 boolean a = test(); 4 System.out.println(a); 5 } 6 7 public static boolean test(){ 8 try{ 9 //因为finally块中包含了return语句 10 //所以下面的return语句失去了作用 11 return true; 12 }finally{ 13 return false; 14 } 15 } 16 }
上面程序中,try块中的return true;语句失去了作用。
当Java程序执行try块、catch块时遇到了return或throw语句,这两个语句都会导致该方法立即结束,但是系统执行这两个语句并不会结束该方法,而是去寻找该异常处理流
程中是否包含finally块,若没有finally块,程序立即执行return或throw语句,该方法终止;若有finally块,系统立即开始执行finally——只有当finally块执行完成后,系统
才会再次跳回来执行try块、catch块里的return或throw语句;若finally块里也使用了return或throw等导致方法终止语句,finally块已经终止了方法,系统将不会跳回去执行
try块、catch块中的任何代码。
尽量避免在finally块中使用return或throw等导致方法终止的语句。
异常处理的嵌套:
1 import java.io.FileInputStream; 2 import java.io.IOException; 3 4 public class FinallyTest{ 5 public static void main(String[] args){ 6 FileInputStream fis = null; 7 try{ 8 fis = new FileInputStream("a.text"); 9 }catch(IOException ioe){ 10 System.out.println(ioe.getMessage()); 11 //return语句强制方法返回 12 //return ; 13 //使用exit退出虚拟机 14 System.exit(1); 15 }finally{ 16 //关闭磁盘文件,回收资源 17 if(fis != null){ 18 try{ 19 fis.close(); 20 }catch(IOException ioe){ 21 ioe.printStackTrace(); 22 } 23 } 24 System.out.println("执行finally块里的资源回收!"); 25 } 26 } 27 }
正如上面程序所示,finally块中也包含了一个完整的异常处理流程,这种在try块、catch块或finally块中包含完整的异常处理流程的情况被称为异常处理的嵌套。
异常处理嵌套的深度没有明确限制,通常,没有必要使用超过两层的嵌套异常处理。
Java7的自动关闭资源的try语句:
Java7增强了try语句的功能——它允许在try关键字后紧跟一对圆括号,圆括号可以声明、初始化一个或多个资源,此处的资源指的是那些必须在程序结束时显示关闭的资源
(比如:数据库连接、网络连接等),try语句在该语句结束时自动关闭这些资源。
需要指出的是:为保证try语句可以正常关闭资源,这些资源实现类必须实现AutoCloseable或Closeable接口,实现这两个接口就必须实现close()方法。
Closeable是AutoCloseable的子接口,可以被自动关闭的资源类要么实现AutoCloseable接口,要么实现Closeable接口。
Closeable接口里的close方法声明抛出了IOException,,因此它的实现类在实现close()方法时只能声明抛出IOException或其子类;
AutoCloseable接口里的close()方法声明抛出了Exception,因此它的实现类在实现close()方法时可以声明抛出任何异常。
1 import java.io.BufferedReader; 2 import java.io.FileReader; 3 import java.io.FileOutputStream; 4 import java.io.PrintStream; 5 import java.io.IOException; 6 7 public class AutoCloseTest{ 8 public static void main(String[] args) throws IOException{ 9 try( 10 //声明、初始化两个可关闭的资源 11 //try语句会自动关闭这两个资源 12 BufferedReader br = new BufferedReader(new FileReader("AutoCloseTest.java")); 13 PrintStream ps = new PrintStream(new FileOutputStream("a.txt")); 14 ){ 15 //使用两个资源 16 System.out.println(br.readLine()); 17 ps.println("庄生晓梦迷蝴蝶"); 18 } 19 } 20 }
这里提醒一句,try()圆括号中的最后一条语句的分号,可要可不要。
上面程序中try语句中声明、初始化了BufferedReader、PrintStream,所以try语句会自动关闭它们。
自动关闭资源的try语句相当于包含了隐式的finally块,因此这个try语句可以既没有catch块,也没有finally块。
Java7几乎把所有的“资源类”(包括文件IO的各种类,JDBC编程的Connection、Statement等接口)进行了改写,改写后的资源类都实现了AutoCloseable或Closeable接口
若程序需要,自动关闭资源的try语句后也可带多个catch块和一个finally块。
Checked异常和Runtime异常体系:
Java异常分为两大类:Checked异常和Runtime异常(运行时异常)。所有的RuntimeException类及其子类的实例被称为Runtime异常;不是RuntimeException类及其子类
的异常实例被称为Checked异常。
Java认为Checked异常都是可以被处理(修复)的异常,所以Java程序必须显示处理Checked异常,若程序没有处理Checked异常,改程序会在编译时报错,无法通过编译
使用throws声明抛出异常:
当前方法不知道如何处理这种类型的异常,该异常应该由上一级调用者处理;若main方法也不知道如何处理这种类型的异常,也可以使用throws声明抛出异常,该异常交给
JVM处理。JVM对异常的处理方法是,打印异常的跟踪栈信息,并终止程序运行,这就是前面程序在遇到异常后自动结束的原因。
throws声明抛出只能在方法签名中使用,throws可以声明抛出多个异常类,多个异常类间以逗号隔开:
throws ExceptionClass1, ExceptionClass2...
上面throws声明抛出的语法格式仅跟在方法签名之后,一旦使用throws语句声明抛出该异常,程序就无需使用try...catch块来捕获该异常了。
1 import java.io.FileInputStream; 2 import java.io.IOException; 3 4 public class ThrowsTest{ 5 public static void main(String[] args) throws IOException{ 6 FileInputStream fis = new FileInputStream("a.txt"); 7 } 8 }
上面程序声明不处理IOException异常,将该异常交给JVM处理,所以一旦程序遇到该异常,JVM就会打印该异常的跟踪栈信息,并结束程序。
若某段代码中调用了一个带throws声明的方法,该方法声明抛出了Checked异常,则表明该方法希望它的调用者来处理该异常。
处理异常的方法有两种,一是调用该方法是放在try块中显示捕获该异常,二是在另一个带throws声明抛出的方法中,交给上一级处理:
1 import java.io.FileInputStream; 2 import java.io.IOException; 3 4 public class ThrowsTest2{ 5 public static void main(String[] args) throws Exception{ 6 //因为test()方法声明抛出IOException异常 7 //所以调用该方法的代码要么处于try...catch块中 8 //要么处于另一个带throws声明抛出的方法中 9 test(); 10 } 11 12 public static void test() throws IOException{ 13 //因为FileInputStream的构造器声明抛出IOException异常 14 //所以调用FileInputStream的代码要么处于try...catch块中 15 //要么处于另一个带throws声明抛出的方法中 16 FileInputStream fis = new FileInputStream("a.txt"); 17 } 18 }
使用throws声明抛出异常时有一个限制,就是方法重写时“两小”中的一条规则:子类方法声明抛出的异常类型应该是父类方法声明抛出的异常类型的子类或相同,子
类方法声明抛出的异常不允许比父类方法声明抛出的异常多。
1 import java.io.FileInputStream; 2 import java.io.IOException; 3 4 public class OverrideThrows{ 5 public void test() throws IOException{ 6 FileInputStream fis = new FileInputStream("a.txt"); 7 } 8 } 9 class Sub extends OverrideThrows{ 10 //子类方法声明抛出了比父类方法更大的异常 11 //所以下面方法出错 12 public void test() throws Exception{ } 13 }
上面程序中Sub子类中的test()方法声明抛出的异常Exception比父类声明抛出异常IOException要大,所以将导致无法通过编译。
由此可见,使用Checked异常至少存在如下两大不便之处:
1.对于程序中的Checked异常,java要求必须显示捕获并处理异常,或显示声明抛出该异常,增加了编程复杂度
2.若在方法中显示声明抛出Checked异常,将会导致方法签名与异常耦合,若该方法是重写父类的方法,则该方法抛出的异常还会受到被重写方法所抛出异常的限制
大部分推荐使用Runtime异常,而不是使用Checked异常,尤其当程序需要自行抛出异常时(如何自行抛出异常下面会讲),使用Runtime异常将更加简洁。
使用throw抛出异常:
当程序出现错误是,系统会自动抛出异常;除此之外,Java也允许程序自行抛出异常,自行抛出异常使用throw语句来完成(注意此处的throw没有后面的s,与前面声明
抛出的throws是有区别的)。
抛出异常:
若需要在程序中自行抛出异常,则应使用throw语句,throw语句可以单独使用,throw语句抛出的不是异常类,而是异常实例,且每次只能抛出一个异常实例。
格式:
throw ExceptionInstance;
改写五子棋游戏处理用户输入的代码:
1 try{ 2 //将用户输入的字符串以逗号(,)作为分隔符,分割成2个字符串 3 String[] posStrArr = inputStr.split(","); 4 //将2个字符串转换成用户下棋的坐标 5 int xPos = Integer.parseInt(posStrArr[0]); 6 int yPos = Integer.parseInt(posStrArr[1]); 7 //把对应的数组元素赋为“O” 8 if(!gb.board[xPos - 1][yPos - 1].equals("+")){ 9 throw new Exception("您输入的坐标点已有棋子了," + "请重新输入"); 10 } 11 gb.board[yPos - 1][xPos - 1] = "O"; 12 }catch (Exception e){ 13 System.out.println("您输入的坐标不合法,请重新输入,"以上是关于第十章.异常处理的主要内容,如果未能解决你的问题,请参考以下文章