文章目录
java异常处理机制
一、异常描述
异常机制已经成为判断一门变成语言是否成熟的标准,除了传统的像C语言没有提供异常机制之外,目前主流的编程语言如Java、C#、Ruby、Python等都提供了成熟的异常机制。异常机制可以使得程序中的异常处理与正常的业务代码分离,保证程序代码更加优雅,并提高程序的健壮性。
对于一个程序设计人员,需要尽可能的预知所有可能发生的情况,尽可能的保证程序在所有糟糕的情况下都可以运行。
- if,else的错误处理机制的缺点?
- 无法穷举所有的异常情况。
- 使得业务实现代码与错误处理代码混杂。
二、异常处理机制
- 什么是java的异常处理机制?
当运行java程序的时候出现异常情况(意外情形),系统会自动生成一个Exception对象来通知程序,从而实现将“业务实现代码”与“异常处理代码”分离,提供更好的可读性。
01、使用try…catch捕获异常
- 流程图
-
语法
try{ 业务实现代码; }catch(Exception e){ 异常处理代码; }
-
注意
- 不论程序代码是否存在try代码块中,甚至在catch块中,只要在执行这块代码的时候出现了异常,系统总会自动生成一个异常对象。
- 捕获异常指的是抛出的异常对象是catch块后异常类及其子类的实例,底层是使用instanceof关键字来进行判断的。
- try块中的变量只在代码块中有效。
02、异常类的继承体系
-
异常层次图
java把所有非正常的情况分为两种:异常(Exception)和错误(Error),它们都继承Throwable类。
-
java内置非检查异常类
异常 描述 ArithmeticException 当出现异常的运算条件时,抛出此异常。例如,一个整数"除以零"时,抛出此类的一个实例。 ArrayIndexOutOfBoundsException 用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引。 ArrayStoreException 试图将错误类型的对象存储到一个对象数组时抛出的异常。 ClassCastException 当试图将对象强制转换为不是实例的子类时,抛出该异常。 IllegalArgumentException 抛出的异常表明向方法传递了一个不合法或不正确的参数。 IllegalMonitorStateException 抛出的异常表明某一线程已经试图等待对象的监视器,或者试图通知其他正在等待对象的监视器而本身没有指定监视器的线程。 IllegalStateException 在非法或不适当的时间调用方法时产生的信号。换句话说,即 Java 环境或 Java 应用程序没有处于请求操作所要求的适当状态下。 IllegalThreadStateException 线程没有处于请求操作所要求的适当状态时抛出的异常。 IndexOutOfBoundsException 指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。 NegativeArraySizeException 如果应用程序试图创建大小为负的数组,则抛出该异常。 NullPointerException 当应用程序试图在需要对象的地方使用 null
时,抛出该异常NumberFormatException 当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。 SecurityException 由安全管理器抛出的异常,指示存在安全侵犯。 StringIndexOutOfBoundsException 此异常由 String
方法抛出,指示索引或者为负,或者超出字符串的大小。UnsupportedOperationException 当不支持请求的操作时,抛出该异常。 -
java内置检查性异常类
异常 描述 ClassNotFoundException 应用程序试图加载类时,找不到相应的类,抛出该异常。 CloneNotSupportedException 当调用 Object
类中的clone
方法克隆对象,但该对象的类无法实现Cloneable
接口时,抛出该异常。IllegalAccessException 拒绝访问一个类的时候,抛出该异常。 InstantiationException 当试图使用 Class
类中的newInstance
方法创建一个类的实例,而指定的类对象因为是一个接口或是一个抽象类而无法实例化时,抛出该异常。InterruptedException 一个线程被另一个线程中断,抛出该异常。 NoSuchFieldException 请求的变量不存在 NoSuchMethodException 请求的方法不存在 -
异常方法
序号 方法及说明 1 public String getMessage() 返回关于发生的异常的详细信息。这个消息在Throwable 类的构造函数中初始化了。 2 public Throwable getCause() 返回一个Throwable 对象代表异常原因。 3 public String toString() 使用getMessage()的结果返回类的串级名字。 4 public void printStackTrace() 打印toString()结果和栈层次到System.err,即错误输出流。 5 public StackTraceElement [] getStackTrace() 返回一个包含堆栈层次的数组。下标为0的元素代表栈顶,最后一个元素代表方法调用堆栈的栈底。 6 public Throwable fillInStackTrace() 用当前的调用栈层次填充Throwable 对象栈层次,添加到栈层次任何先前信息中。
03、Java7提供的多异常捕获
在java7以前,每一个catch块只能捕获一种类型的异常;但是从java7开始,一个catch块可以捕获多种类型的异常。
-
注意
- 捕获多种类型的异常的时候,多种异常类型之间用“|”隔开。
- 捕获多种异常类型的时候,异常变量有隐式的final修饰,因此程序不能对异常变量重新赋值。
-
实例
public class Main { public static void main(String[] args) { try{ int a = Integer.parseInt(args[0]); int b = Integer.parseInt(args[1]); int c = a/b; System.out.println("您输入的两个数的相除结果是:" + c); }catch (IndexOutOfBoundsException|NumberFormatException|ArithmeticException ie){ System.out.println("程序发生了数组越界、数字格式异常、算数异常之一"); //捕获异常的时候,异常变量被final修饰不能重新赋值 //ie = new ArithmeticException(); }catch (Exception e){ System.out.println("未知异常"); e = new RuntimeException("test"); } } } 程序发生了数组越界、数字格式异常、算数异常之一 Process finished with exit code 0
04、访问异常信息
-
常用方法
- getMessage():返回该异常的详细描述字符串。
- printStackTrace():将该异常的跟踪栈信息输出到标准错误输出。
- printStackTrace(PrintStream s):将该异常的跟踪栈信息输出到指定输出流。
- getStackTrace():返回该异常的跟踪栈信息。
-
实例
public static void main(String[] args) { try{ FileInputStream fis = new FileInputStream("a.txt"); }catch (IOException e){ System.out.println(e.getMessage()); e.printStackTrace(); } } a.txt (系统找不到指定的文件。)【这是异常的详细描述字符串】 java.io.FileNotFoundException: a.txt (系统找不到指定的文件。)【这是异常的追踪栈信息】 at java.io.FileInputStream.open0(Native Method) at java.io.FileInputStream.open(FileInputStream.java:195) at java.io.FileInputStream.<init>(FileInputStream.java:138) at java.io.FileInputStream.<init>(FileInputStream.java:93) at Main.main(Main.java:9)
05、使用finally回收资源
有些时候,程序在try块中打开了一些物理资源(例如数据库连接、网络连接和磁盘文件等),这些物理资源都必须显式回收。
为了保证一定能回收try块中打开的物理资源,异常处理提供了finally块。不管try块中代码是否出现异常,也不管哪一个catch块被执行,甚至在try块或者catch块中执行了return语句,finally块总会被执行。
-
语法
try{ 业务代码实现; }catch(SubException e){ 异常处理块; }catch(SubException2 e){ 异常处理块; } ... finally{ 资源回收块; }
-
注意
- 如果在异常处理代码中使用System.exit(1)语句来退出虚拟机,那么finally块将失去被执行的机会。
- 除非在try块,catch块中调用了退出虚拟机的方法,否则不管在try块、catch块中执行怎样的代码,出现在怎样的情况,异常处理的finally块总会被执行。
- 一旦在finally块中执行了return和throw语句,将会导致try块、catch块中的return、throw语句失效。
- 如果在finally块里也使用了return和throw等导致方法终止的语句,finally块已经终止了方法,系统不会再跳回try块、catch块中执行任何代码。
-
小结
异常处理语法结构中只有try块是必须的,没有try块,就不会有catch块和finally块,try块后面可以跟catch块和finally块的其中一个也可以同时跟两个。可以有多个catch块出现,但是父类异常类型的catch块要出现在子类异常catch块的后面,多个catch块必须位于try块之后,finally块必须位于所有的catch块之后。
06、异常处理的嵌套
异常处理流程代码可以放在任何能放可执行性代码的地方,因为完整的异常处理流程即可以放在try块中也可以放在catch块和finally块中。
07、自动关闭资源的try语句
java7增强了try语句的功能——允许在try关键字后紧跟一对圆括号,圆括号可以声明、初始化一个或多个资源。
-
注意
- 为了保证try语句可以正常关闭资源,这些资源实现类必须实现AutoCloseable或Closeable接口,实现这两个接口就必须实现close方法。
- java7几乎把所有的“资源类”(包括文件的IO的各种类、JDBC变成的Connection、Statement接口)进行了改写,改写后的资源类都实现了上述接口。
-
实例
public static void main(String[] args) { try( //声明、初始化两个可关闭的资源 //try语句会自动关闭这两个资源 BufferedReader br = new BufferedReader(new FileReader("AutoCloseTest.txt")); PrintStream ps = new PrintStream(new FileOutputStream("a.txt")); ){ //使用两个资源 System.out.println(br.readLine()); ps.println("庄生晓梦迷蝴蝶"); } }
三、Checked(检查)异常和Runtime(非受查)异常体系
01、两者关系与区别
-
概念区别
java的异常被分为两大类:Checked异常和Runtime异常(运行时异常)。所有的RuntimeException类及其子类的实例被称为Runtime异常;不是RunTimeException类及其子类的实例则被称为Checked异常。
如果程序没有处理Checked异常,则程序在编译期间都会发生错误,无法通过编译,在编译器中的体现就是代码下面有红线。
Runtime异常更加灵活,Runtime异常无须显式声明抛出,如果程序需要捕获RunTime异常,可以使用try…catch块实现。
-
处理Checked异常的方式
- 当前方法明确知道如何处理异常,程序应该使用try…catch块来捕获异常然后在对应的catch块中修复异常。
- 当前方法不知道如何处理异常,应该在定义该方法的时候抛出异常。
02、使用throws声明抛出异常
使用throws声明抛出异常的思路是,当前方法不知道如何处理这种类型的异常,该异常应该由上一级调用者处理,如果main方法也不知道如何处理这种异常,也可以使用throws来抛出异常,该异常将会被JVM处理。
JVM对异常的处理方法是,打印异常的跟踪栈信息,并中止程序运行。
异常跟踪栈信息:逐层定位异常代码所在地点。
-
实例
public static void main(String[] args) throws FileNotFoundException { test(); } public static void test() throws FileNotFoundException { FileInputStream fis = new FileInputStream("a.txt"); }
-
注意
- 一旦使用了throws来抛出异常,程序就可以不实用try…catch代码块来捕获异常了。
- 使用throws抛出异常时候有一个限制,子类方法抛出的异常类型应该是父类方法声明抛出异常类型的子类或相同,子类方法抛出的异常不允许比父类方法抛出的异常多。
- 对于Checked异常,java要求必须显式捕获处理该异常(try…catch),或者显示声明抛出异常(throws)。
-
Checked异常的优势
- Checked异常能在编译的时候提醒程序员代码可能存在的问题,提醒程序员必须注意处理该异常。
- 声明该异常由该方法的调用者来处理,从而可以避免程序员因为粗心而忘记处理该异常的错误。
四、使用throw抛出异常
java允许自行抛出异常,在自行抛出异常的时候,我们使用throw语句来完成。
01、使用throw抛出异常
-
实例
由于与业务需求不符合而产生的异常,必须由程序员来决定抛出,系统无法抛出这种异常。throw语句抛出的不是异常类而是异常实例,而且每次只能抛出一个异常实例。
try{ 业务实现代码; if(...){ throw new Exception("一个异常"); } }catch(Exception e){ ... }
-
注意
- 如果throw语句抛出的异常是Checked异常,则该throw语句要么处于try块中,显式捕获异常,要么放在一个带有throws声明抛出的方法中,即把该异常交给该方法的调用者来处理。
- 如果throw语句抛出的异常是RunTime异常,该语句无须放在try块中,也无须放在throws声明抛出的方法中,程序接可以显式捕获并处理异常也可以显式声明抛出异常。
02、自定义异常类
用户自定义异常都应该继承Exception基类,如果希望自定义Runtime异常,则应该继承RuntimeException基类。定义异常类的时候通常需要提供两个构造器,**一个是无参构造器,另一个是带一个字符串参数的构造器。**这个字符串将作为这个异常对象的描述信息(也就是异常对象的getMessage()方法的返回值)。
-
实例
class AuctionException extends Exception{ public AuctionException(){ } public AuctionException(String msg){ super(msg); } }
-
自定义异常类的主要目的
在大部分情况下,自定义异常类都可以像上述类一样,只需要更改一下类的名称,使得该异常类的类名可以更加准确的描述该异常。
03、catch与throw同时使用
- 前面两种异常处理方式
- 在出现异常的方法中捕获并处理异常,方法的调用者不用再捕获异常。
- 在出现异常的方法中显式声明抛出异常,这样一来,方法的调用者需要捕获并处理异常。
04、java7增强的throw语句
- 代码分析
- Java 6对该代码理解:Java编译器处理“简单而粗暴”——由于在捕获该异常时声明ex类型是Exception,因此Java编译器认为这段代码可能抛出Exception异常,所以包含这段代码的方法通常需要声明抛出Exception异常。
- Java 7对该代码理解:Java编译器会执行更细致的检查,Java 编译器会检查throw语句抛出异常的实际类型,这样编译器知道①号代码处实际上只可能抛出FileNotFoundException异常,因此在方法签名中只声明抛出FileNotFoundException异常即可。
05、异常链
-
使用异常链原因
- 对于正常用户而言,他们不想看到底层的异常,底层异常对他们没有使用系统没有任何帮助。
- 对于恶意用户而言,底层异常暴露出来会使得系统不安全。
-
处理方法
程序先捕获原始异常,然后抛出一个新的业务异常,新的业务异常中包含了对用户的提示信息,这种处理方式被称为异常转译。
这种捕获一个异常然后接着抛出另一个新的异常,并把原始异常信息保存下来是一种典型的链式处理(23种设计模式之一的责任链模式),也称为“异常链”。
五、java的异常跟踪栈
异常对象的printStackTrace()方法用于打印异常对象的跟踪栈信息,根据这个方法的输出结果,开发者可以找到异常的源头,并跟踪到异常一路触发的过程。
-
实例
public class Main { public static void main(String[] args) throws AuctionException { first(); } public static void first() throws AuctionException { second(); } public static void second() throws AuctionException { third(); } public static void third() throws AuctionException { throw new AuctionException("是异常"); } } class AuctionException extends Exception{ public AuctionException(){ } public AuctionException(String msg){ super(msg); } Exception in thread "main" AuctionException: 是异常 at Main.third(Main.java:17) at Main.second(Main.java:14) at Main.first(Main.java:11) at Main.main(Main.java:7) Process finished with exit code 1
六、异常处理规则
01、不要过度使用异常
02、不要使用过于庞大的try块
03、避免使用Catch all语句
04、不要忽略捕获到的异常
七、总结
本篇内容主要介绍了java异常处理机制的相关知识,java的异常处理主要依赖于try、catch、finally、throw和throws关键字,本篇详细讲解了这5个关键字的用法。本篇还介绍了java异常类之间的继承关系,并介绍了Checked异常和Runtime异常之间的区别。本篇也详细介绍了Java7对异常处理的增强。本篇还详细的讲解了实际开发中最常用的异常链和异常转椅,最后从优化程序的角度给出了实际应用中处理异常的几条基本规则。