《疯狂java讲义》第10章 异常处理

第10章 异常处理

异常机制可以使程序中的异常处理代码和正常业务代码分离,保证程序代码更加优雅,并可以提高程序的健壮性。
Java的异常机制主要依赖于try、catch、finally、throw和throws五个关键字:

(1)try关键字后紧跟一个花括号扩起来的代码块(花括号不可省略),简称try块,它里面放置可能引发异常的代码。
(2)catch后对应异常类型和一个代码块,用于表明该catch块用于处理这种类型的代码块。
(3)多个catch块后还可以跟一个finally块,finally块用于回收在try块里打开的物理资源,异常机制会保证finally块总被执行。
(4)throws关键字主要在方法签名中使用,用于声明该方法可能抛出的异常;
(5)throw用于抛出一个实际的异常,throw可以单独作为语句使用,抛出一个具体的异常对象。

10.2 异常处理机制

实现将“业务功能实现代码”和“错误处理代码”分离。

10.2.1 使用try…catch捕获异常

try{
	//业务实现代码
	...
}catch(Exception e){
	//错误处理代码
	...
}
  • 如果执行try块里的业务逻辑代码时出现异常,系统自动生成一个异常对象,该异常对象被提交给Java运行时环境,这个过程被称为抛出(throw)异常
  • 当Java运行时环境收到异常对象时,会寻找能处理该异常对象的catch块,如果找到合适的catch块,则把该异常对象交给该catch块处理,这个过程被称为捕获(catch)异常
  • 如果Java运行时环境找不到捕获异常的catch块,则运行时环境终止,Java程序也将退出。

10.2.2 异常类的继承体系

  • try块里声明的变量是代码块内局部变量,它只在try块内有效,在catch块中不能访问该变量。在这里插入图片描述
  • Java把所有的非正常情况分成两种:异常(Exception)错误(Error),它们都继承Throwable父类
    在这里插入图片描述
  • Error 错误,一般是指与虚拟机相关的问题,如系统崩溃、虚拟机错误、动态链接失败等,这种错误无法恢复或不可能捕获,将导致应用程序中断。通常应用程序无法处理这些错误,因此应用程序不应该试图使用catch块来捕获Error对象。在定义该方法时,也无须在其throws子句中声明该方法可能抛出Error及其任何子类。
  • 程序运行时的常见异常:

(1)如果运行该程序时输入的参数不够,将会发生数组越界异常,Java运行时将调用IndexOutOfBoundsException对应的 catch 块处理该异常。
(2)如果运行该程序时输入的参数不是数字,而是字母,将发生数字格式异常,Java运行时将调用NumberFormatException对应的catch块处理该异常。
(3)如果运行该程序时输入的第二个参数是0,将发生除0异常,Java运行时将调用ArithmeticException对应的catch块处理该异常。
(4)如果程序运行时出现其他异常,该异常对象总是Exception类或其子类的实例,Java运行时将调用Exception对应的catch块处理该异常。

  • 异常捕获时,一定要记住捕获异常,捕获异常。

10.2.3 java 7 新增的多异常捕获

  • (1)多种异常类型之间用竖线( | )隔开;(2)异常变量有隐式的final修饰,程序不能对异常变量重新赋值。
try{
	...
}catch(IndexOutOfBoundsException|NumberFormatException|ArithmeticException ie){
	...
}

10.2.4 访问异常信息

  • 所有的异常对象都包含了如下几个常用方法。

getMessage():返回该异常的详细描述字符串。
printStackTrace():将该异常的跟踪栈信息输出到标准错误输出
printStack Trace(PrintStreams):将该异常的跟踪栈信息输出到指定输出流
getStackTrace():返回该异常的跟踪栈信息。

try{
	...
}catch(IOException ioe){
	System.out.println(ioe.getMessage());
	ioe.printStackTrace();
}

10.2.5 使用finally回收资源

  • 程序在try块里打开了一些物理资源(例如数据库连接、网络连接和磁盘文件等),这些都必须显式回收
  • finally块:保证一定能回收try块中打开的物理资源。finally块总会被执行
  • 异常处理语法结构中只有try块是必需的,catch块和finally块都是可选的,但至少出现其中之一。
try{
	...
}catch(SubException e){
	...
}finally{
	//资源回收块
}
  • 如果在异常处理代码中使用System.exit(1)语句来退出虚拟机,则finally块将失去执行的机会。
  • 不要在finally 块中使用如return或throw等导致方法终止的语句,将会导致try块、catch块中的return、throw语句失效(先到finally块中执行,直接结束)。

10.2.6 异常处理的嵌套

  • 在try块、catch块或finally块中包含完整的异常处理流程的情形被称为异常处理的嵌套
  • 通常没有必要使用超过两层的嵌套异常处理。

10.2.7 java 9 增强的自动关闭资源的try语句

  • java 7 允许在try关键字后紧跟一对圆括号,圆括号可以声明初始化一个或多个资源,那些必须在程序结束时显式关闭的资源(比如数据库连接、网络连接等),try 语句在该语句结束时自动关闭这些资源
try(
	BufferedReader br = new BufferedReader(new FileReader("test.java"));
	PrintStream ps = new PrintStream(new FileOutputStream("a.txt"));
){
	...
}
  • 自动关闭资源的try语句相当于包含了隐式的finally块(可以没有catch,finally块)。
  • java 9 不要求在try后的圆括号内声明并创建资源,只需要自动关闭的资源有final修饰或者是有效的final(effectively final),允许将资源变量放在try后的圆括号内。
//有final修饰的资源
final BufferedReader br = new BufferedReader(new FileReader("test.java"));
//没有显示使用final修饰,但只要不对该变量重新赋值,就是有效的final
PrintStream ps = new PrintStream(new FileOutputStream("a.txt"));
//只要将两个资源放在try后的圆括号内即可
try(br;ps){
	...
}

10.3 Checked异常和Runtime异常体系

  • Java的异常被分为两大类:Checked异常Runtime异常(运行时异常)。所有的RuntimeException类及其子类的实例被称为Runtime异常;不是RuntimeException类及其子类的异常实例则被称为Checked异常。
  • 对于Checked异常的处理方式有如下两种。

(1)当前方法明确知道如何处理该异常,程序应该使用try…catch块来捕获该异常,然后在对应的catch块中修复该异常。
(2)当前方法不知道如何处理这种异常,应该在定义该方法时声明抛出该异常。

  • Runtime异常则更加灵活,Runtime异常无须显式声明抛出,如果程序需要捕获Runtime异常,也可以使用try…catch块来实现。
  • Checked异常体现了java的严谨性,增加了程序的健壮性,但也降低了程序开发的生产率和代码的执行效率。

10.3.1 使用throws声明抛出异常

  • throws情况:当前方法不知道如何处理这种类型的异常。
  • throws声明抛出只能在方法签名中使用,可以声明抛出多个异常类。
throws ExceptionClass1, ExceptionClass2..
  • throws IOException,声明 不处理IOException异常,将该异常交给JVM处理,JVM会打印该异常的跟踪栈信息,并结束程序。
  • 方法希望他的调用者来处理该异常,调用方法时放在try块中显示捕获该异常,或者放在另一个带throws声明抛出的方法中。

10.3.2 方法重写时声明抛出异常的限制

  • 使用throws声明抛出异常有一个限制,就是方法重写时的“两小”中的一条规则:子类方法声明抛出的异常类型应该是父类方法声明抛出的异常类型的子类或者相同。
  • 使用Checked异常至少存在两大不便:(1)Checked异常,要求必须显式捕获并处理,增加了编程复杂度。(2)方法中显式声明抛出的Checked异常,将导致方法签名与异常 耦合,如果是重写父类的方法,该方法抛出的异常还会受到被重写方法所抛出异常的限制。
  • Checked异常优点:能在编译时提醒程序员代码可能存在问题。
  • 推荐使用Runtime异常。(保证健壮性,避免烦琐性)

10.4 使用throw抛出异常

10.4.1 抛出异常

  • 如果需要在程序中自行抛出异常,则应使用throw语句,throw语句可以单独使用,throw 语句抛出的不是异常类,而是一个异常实例,而且每次只能抛出一个异常实例。
  • throw语句的语法格式如下:throw ExceptionInstance;
  • 如果throw语句抛出的异常是Checked异常,则该throw语句处于try块里,显式捕获该异常,或放在一个带 throws声明抛出的方法中,即把该异常交给该方法的调用者处理
  • 如果 throw 语句抛出的异常是Runtime异常,程序可以显式使用try…catch来捕获并处理该异常,也可以完全不理会该异常,把该异常交给该方法调用者处理

10.4.2 自定义异常类

  • 用户自定义异常都应该继承Exception基类,如果希望自定义Runtime异常,则应该继承RuntimeException基类。定义异常类时通常需要提供两个构造器:一个是无参数的构造器;另一个是带一个字符串参数的构造器,这个字符串将作为该异常对象的描述信息(也就是异常对象的 getMessage()方法的返回值)。
public class AuctionException extends Exception{
	//无参数的构造器
	public AuctionException(){..}
	//带一个字符串参数的构造器
	public AuctionException(String msg){
		super.(msg);
	}
}
  • 在大部分情况下,创建自定义异常都可采用与AuctionException.java相似的代码完成,只需改变AuctionException异常的类名即可,让该异常类的类名可以准确描述该异常。

10.4.3 catch和throw同时使用

  • 几个方法协作才可完全处理该异常,程序对异常进行部分处理,还有些处理需要在该方法的调用者中才能完成,所以应该再次抛出异常,让该方法的调用者也能捕获到异常。
  • 可以在catch块中结合throw语句来完成。
catch (Exception e){
	//此处完成本方法中可以对异常执行的修复处理
	//此处仅仅是在控制台打印异常的跟踪栈信息
	e.printStackTrace();
	//再次抛出自定义异常
	throw new AuctionException("须为数字,不能包含其他字符");
}

10.4.4 java 7 增强的throw语句

  • 代码中再次抛出了捕获到的异常,声明该异常的类型为Exception;但实际上try块中可能只调用了FileOutputStream构造器,这个构造器声明只是抛出了FileNotFoundException异常
  • 从Java7开始,Java编译器执行更细致的检查,Java编译器会检查throw语句抛出异常的实际类型,这样编译器知道代码处实际上只可能抛出FileNotFoundException异常,因此在方法签名中只要声明抛出FileNotFoundException异常即可

10.4.5 异常链

  • 底层的原始异常直接传给用户是一种不负责任的表现。
  • 通常的做法是:程序先捕获原始异常,然后抛出一个新的业务异常,新的业务异常中包含了对用户的提示信息,这种处理方式被称为异常转译
  • 这种把捕获一个异常然后接着抛出另一个异常,并把原始异常信息保存下来是一种典型的链式处理(23种设计模式之一:职责链模式),也被称为“异常链”。
  • 从JDK1.4以后,所有Throwable的子类在构造器中都可以接收一个cause对象作为参数,用来表示原始异常,这样可以把原始异常传递给新的异常,使得即使在当前位置创建并抛出了新的异常,你也能通过这个异常链追踪到异常最初发生的位置。

10.5 java的异常跟踪栈

  • 异常对象的printStackTrace() 方法 用于打印 异常的跟踪栈信息,可以找到 异常的源头,并跟踪到异常一路触发的过程。
  • 程序运行时经常会发生一系列方法调用,从而形成“方法调用栈”,异常的传播则相反:只要异常没有被完全捕获(包括异常没有被捕获,或异常被处理后重新抛出了新异常),异常从发生异常的方法逐渐向外传播,首先传给该方法的调用者,直至最后传到main方法,如果main方法依然没有处理该异常,JVM会中止该程序,并打印异常的跟踪栈信息
  • printStackTrace()方法可以用它来调试程序,但在最后发布的程序中,应该避免使用它;而应该对捕获的异常进行适当的处理,而不是简单地将异常的跟踪栈信息打印出来。

10.6 异常处理规则

成功的异常处理应该实现如下4个目标。

使程序代码混乱最小化。
捕获并保留诊断信息。
通知合适的人员。
采用合适的方式结束异常活动。

10.6.1 不要过度使用异常

  • 异常只应该用于处理非正常的情况,不要使用异常处理来代替正常的流程控制。对于一些完全可预知,而且处理方式清楚的错误,程序应该提供相应的错误处理代码,而不是将其笼统地称为异常。

10.6.2 不要使用过于庞大的try块

  • 正确的做法是,把大块的try块分割成多个可能出现异常的程序段落,并把它们放在单独的try块中,从而分别捕获并处理异常。

10.6.3 避免使用Catch All语句

  • 所谓 Catch All 语句指的是一种异常捕获模块,它可以处理程序发生的所有可能异常
  • 所有的异常都采用相同的处理方式,这将导致无法对不同的异常分情况处理。
  • 这种捕获方式可能将程序中的错误、Runtime异常等可能导致程序终止的情况全部捕获到,从而“压制”了异常。

10.6.4 不要忽略捕获到的异常

  • 不要忽略异常!既然已捕获到异常,那catch块理应做些有用的事情——处理并修复这个错误。catch块整个为空,或者仅仅打印出错信息都是不妥的!
  • 处理异常;重新抛出新异常;在合适的层处理异常。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值