异常处理的合理性完整性体现了一门语言是否成熟。而Java作为目前最流行的开发语言之一,固然具有一个完善的异常处理机制。本文将对异常处理机制来一次大的总结,后面讲解一些原则性问题、开发技巧以及经常遇到的异常。

文章结构:

1.异常机制树的讲解;
2.异常处理的五大关键字;
3.原则性问题以及开发使用异常技巧;
4.异常链;
5.Java的一些新特性;
6.常见的异常类(这个查找多个文章总结)


一、先来幅异常机制树图

JAVA 系统容错方案 java容错纠错机制_java


在java中,所有的非正常情况都继承Throwable。而Error和Exception是他的两个子类,各自又含有一系列的子类,对应一系列的错误和异常。这里可以看出java设计的特性与原则。

Error(错误):

是程序无法处理的,指与虚拟机相关的问题(比如:系统崩溃、虚拟机错误)。这些错误无法恢复或者捕获

Exception(异常):

是程序本身可处理的。而异常又分为两大类:Checked异常和Runtime异常(运行时异常)。

Checked异常是指可以在编译阶段内处理的异常,java认为必须显示处理的异常。处理方式:1.当前方法明确如何处理该异常,就应该捕获它,在对应的catch中修复 2.当前方法不知道如何处理该异常,应在定义该方法时声明抛出该异常。它的缺点:1.Java要求必须显示捕获并处理该异常,增加编程复杂度 2.在方法显示声明抛出此异常,会导致方法签名与异常耦合。它的优点:可在编译时提醒程序员代码可能存在的问题,提醒处理该异常。

Runtime异常是指编译阶段无须处理,debug过程处理的异常(因为java编译器不会检查它,可以通过编译)。它的优势:1.正常代码与错误处理代码的分离;2.保证程序健壮性;3.避免Checked异常的编程繁琐性。

剩余的异常均是这两个的子类,这里不过多描述,每个的异常是不同的,太多了。


二、异常处理的五大关键字:

java异常处理机制核心思想:抛出异常,捕获异常,处理异常

抛出异常:

当一个方法出现错误引发异常时,方法创建异常对象并交付运行时系统,异常对象中包含了异常类型和异常出现时的程序状态等异常信息。运行时系统负责寻找处置异常的代码并执行。

捕获异常:

在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器(exception handler)。潜在的异常处理器是异常发生时依次存留在调用栈中的方法的集合。当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,即为合适 的异常处理器。运行时系统从发生异常的方法开始,依次回查调用栈中的方法,直至找到含有合适异常处理器的方法并执行。当运行时系统遍历调用栈而未找到合适 的异常处理器,则运行时系统终止。同时,意味着Java程序的终止。

处理异常:

在异常被捕获到后,我们需要对可以处理的异常进行处理,在这一级的异常能处理的就在此处理,在这一级处理不了的就让继续往上抛异常,交由更上一级去处理。这就是开发的时候的异常处理逻辑树。

五大关键字详解:

try:

作用:里面放置可能引发异常的代码;
特点:在try里面声明的变量是代码块内的局部变量,只在try块内有效,catch块也不能访问该变量。变量不是指物理资源

catch:

作用:用于处理某一类型的代码块

finally:

作用:用于回收在try块打开的物理资源,异常机制保证finally会被执行(物理资源:如数据库连接、网络连接和磁盘文件等);
特点:1.即使try和catch有return语句也会执行,但是如果try和catch有System.exit(1)语句来退出虚拟机,就不会执行finally里面的语句; 2.不要在其中使用return或throw等导致方法终止的语句,否则会导致很多奇怪的情况

Java垃圾回收机制不会回收任何物理资源,回收机制只回收堆内存中的对象所占用的内存。所以要在finally去回收

//此处给出一例子
  try {  
    // 可能会发生异常的程序代码  
} catch (Type1 id1) {  
    // 捕获并处理try抛出的异常类型Type1  
} catch (Type2 id2) {  
    // 捕获并处理try抛出的异常类型Type2  
} finally {  
    // 无论是否发生异常,都将执行的语句块  
}
try-catch-finally执行顺序:

1)当try没有捕获到异常时:try语句块中的语句逐一被执行,程序将跳过catch语句块,执行finally语句块和其后的语句;

2)当try捕获到异常,catch语句块里没有处理此异常的情况:当try语句块里的某条语句出现异常时,而没有处理此异常的catch语句块时,此异常将会抛给JVM处理,finally语句块里的语句还是会被执行,但finally语句块后的语句不会被执行;

3)当try捕获到异常,catch语句块里有处理此异常的情况:在try语句块中是按照顺序来执行的,当执行到某一条语句出现异常时,程序将跳到catch语句块,并与catch语句块逐一匹配,找到与之对应的处理程序,其他的catch语句块将不会被执行,而try语句块中,出现异常之后的语句也不会被执行,catch语句块执行完后,执行finally语句块里的语句,最后执行finally语句块后的语句;

throw:

作用:用于抛出一个实际异常,程序自行抛出异常。可以单独作为语句使用,抛出一个具体的异常对象;
注意:可以单独使用,抛出的不是异常类,而是一个异常实例,且每次只能抛出一个异常实例!!是一个实例!。

throws:

作用:在方法签名中使用,用于声明该方法可能抛出的异常;
原理思路:当前方法不知道怎么处理这一类型的异常,需要交由上一级调用者去处理;规则::子类方法声明抛出的异常类型应该是父类方法声明抛出的异常类型的子类或者与之相同。子类方法声明抛出的异常不允许比父类方法声明抛出的异常大。

//此处给出的例子包含throws、throw的运用,而且扩展了一些东西--自定义异常类(到了项目工程,为了异常层级清晰,层级传递,一般需要我们去自定义异常类来实现逻辑层层处理)
//1.分析过程:我们可以先看到我们自定义了一个普通的异常类,然后看到main方法里面,该类对象使用了一个方法签名中使用throws的方法,然后他进行了一系列的异常抛出、捕获,接着在异常处理抛出我们的自定义异常类,然后打印异常栈信息。
//2.一些注解:2.标识可能抛出的异常: throws 异常类名1,异常类名2 
/*.捕获异常: 
try{} 
catch(异常类名 y){} 
catch(异常类名 y){} 
.方法解释 
getMessage() //输出异常的信息 
printStackTrace() //输出导致异常更为详细的信息 
*/

public class AuctionTest
{
	private double initPrice = 30.0;
	// 因为该方法中显式抛出了AuctionException异常,
	// 所以此处需要声明抛出AuctionException异常
	public void bid(String bidPrice)
		throws AuctionException
	{
		double d = 0.0;
		try
		{
			d = Double.parseDouble(bidPrice);
		}
		catch (Exception e)
		{
			// 此处完成本方法中可以对异常执行的修复处理,
			// 此处仅仅是在控制台打印异常跟踪栈信息。
			e.printStackTrace();
			// 再次抛出自定义异常
			throw new AuctionException("竞拍价必须是数值,"
				+ "不能包含其他字符!");
		}
		if (initPrice > d)
		{
			throw new AuctionException("竞拍价比起拍价低,"
				+ "不允许竞拍!");
		}
		initPrice = d;
	}
	public static void main(String[] args)
	{
		AuctionTest at = new AuctionTest();
		try
		{
			at.bid("df");
		}
		catch (AuctionException ae)
		{
			// 再次捕捉到bid方法中的异常。并对该异常进行处理
			System.err.println(ae.getMessage());
		}
	}
}
class AuctionException extends Exception { // 创建自定义异常类  
    String message; // 定义String类型变量  
    public AuctionException (String ErrorMessagr) { // 父类方法  
        message = ErrorMessagr;  
    }

补充:自定义异常类的做法:

1.自定义异常: 
 
class 异常类名 extends Exception 
{ 
    public 异常类名(String msg) 
    { 
        super(msg); 
    } 
}  
或者:
class 异常类名 extends Exception 
{ 
    String message; // 定义String类型变量  
    public 异常类名(String ErrorMessagr) { // 父类方法  
        message = ErrorMessagr;  
    }  
    public String getMessage() { // 覆盖getMessage()方法  
        return message;  
    }  
}

使用:继承异常父类实现子类,一般只需改变自定义异常的类名即可,让该异常类的类名可准确描述该异常。

企业级应用异常处理:1.应用后台需要通过日志来记录异常发生的详细情况; 2.应用还需根据异常向应用使用者传达某种提示


三、原则性问题以及开发使用异常技巧

原则性问题-异常处理机制初衷:

1.将不可预期异常的处理代码和正常的业务逻辑处理代码分离。
2.异常只应该用于处理非正常情况,不要用异常处理来代替正常流程控制。(对于完全可知的、处理方式清晰的错误,程序本应该提供相应的错误代码,而不是笼统称为异常)
3.先捕获小异常,再捕获大异常(Exception e 用此表示未知异常)
4.对于完全已知的错误应该编写处理这种错误的代码,增加程序健壮性。

开发使用异常技巧:
1.不要过度使用异常。

原因:1.把异常和普通错误混淆在一起,不再编写任何错误处理代码,而是以简单地抛出异常来代替所有的错误处理;2.使用异常处理来代替流程控制。

2.不要使用过于庞大的try块。

原因:1.代码块太大,业务太复杂就会出现异常的可能性大大增加;2.try块过大,就需要大量的catch来分析它们的逻辑,增加编程复杂度

3.避免使用Catch All语句。

定义:Catch All指的是一种异常捕获模块,可以处理程序发生的所有可能异常。
原因:1.所有异常都采用相同处理方式,会导致无法对不同的异常分情况处理,这时如果要分情况处理,则需要在catch使用分支语句(switch),这样得不偿失。2.这种捕获可能将程序的错误、Runtime异常可能导致程序终止的情况都全部捕获,从而“压制”异常。如果出现“关键异常”也会被“悄悄”忽略

4.不要忽略捕获到的异常。

原因:已经捕获到异常不处理或者仅仅打印跟踪栈几句话是于事无补的。
做法:一、对于Cheacked异常,都应该尽量修复。二、当前环境能处理的尽量处理,然后进行异常转译,异常打包成当前层异常,重新抛给上层调用者。三、如果当前层不清楚如何处理,就不要用catch捕获,直接用throws声明抛出该异常,让上层调用者去处理。


四、异常链:

JAVA 系统容错方案 java容错纠错机制_java_02

企业应用做法:程序先捕获原始异常,然后抛出一个新的业务异常,新的业务异常中包含对用户的提示信息-----这种做法叫异常转译。因为核心是:在合适的层级处理异常。

异常的打印:Java的异常跟踪栈,printSrackTrace()方法–用于打印异常的跟踪栈信息。这个设计体现了一个设计模式-职责链模式。这个异常传播方式:只要异常没被完全捕获,异常从发生异常的方法逐渐向外传播,首先传给方法的调用者,该方法调用者再传给其调用者,直至传至上一层的方法,一层层传递。如果最上层的方法还没处理该异常,JVM就会中止程序,并打印异常的跟踪栈信息。

五、Java的一些新特性方便了开发完善了逻辑

1.Java7的多异常捕获

一个catch块可捕获多种类型的异常

2.Java7的自动关闭资源的try语句。

定义:允许在try后面紧跟一堆圆括号,圆括号可以声明、初始化多个物理资源,当try结束时自动关闭这些资源。
实现基础:那些资源实现类实现了AutoCloseable或Closeable接口。

3.SalException业务异常类。

用来封装原始异常,从而实现对异常的链式处理


六.常见的异常类:

(1). RuntimeException子类:
1、 java.lang.ArrayIndexOutOfBoundsException

数组索引越界异常。当对数组的索引值为负数或大于等于数组大小时抛出。

2、java.lang.ArithmeticException

算术条件异常。譬如:整数除零等。

3、java.lang.NullPointerException

空指针异常。当应用试图在要求使用对象的地方使用了null时,抛出该异常。譬如:调用null对象的实例方法、访问null对象的属性、计算null对象的长度、使用throw语句抛出null等等

ClassCastException 搜索- 类型强制转换异常
IllegalArgumentException - 传递非法参数异常
4、java.lang.ClassNotFoundException

找不到类异常。当应用试图根据字符串形式的类名构造类,而在遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异常。

5、java.lang.NegativeArraySizeException

数组长度为负异常

6、java.lang.ArrayStoreException

数组中包含不兼容的值抛出的异常

7、java.lang.SecurityException

安全性异常

8、java.lang.IllegalArgumentException

非法参数异常

(2).IOException

IOException:操作输入流和输出流时可能出现的异常。
EOFException 文件已结束异常
FileNotFoundException 文件未找到异常

(3). 其他

ClassCastException 类型转换异常类
ArrayStoreException 数组中包含不兼容的值抛出的异常
SQLException 操作数据库异常类
NoSuchFieldException 字段未找到异常
NoSuchMethodException 方法未找到抛出的异常
NumberFormatException 字符串转换为数字抛出的异常
StringIndexOutOfBoundsException 字符串索引超出范围抛出的异常
IllegalAccessException 不允许访问某类异常
InstantiationException 当应用程序试图使用Class类中的newInstance()方法创建一个类的实例,而指定的类对象无法被实例化时,抛出该异常


JackFrost的博客