疯狂Java讲义读书笔记09 Java异常处理

异常机制已经成为判断一门编程语言是否成熟的标准,除了传统的像C语言没有提供异常机制之外,目前主流的编程语言都提供了成熟的异常机制。

异常机制可以使程序中的异常处理代码和正常业务代码分离,保证程序代码更加优雅,并可以提高程序的健壮性。

Java的异常机制主要依赖于try catch finally throw throws

Java7进一步增强了异常处理机制的功能,包括带资源的try语句、捕获多异常的catch两个新功能。

这两个功能极大的简化了异常处理

开发者都希望所有的错误都能在编译阶段被发现,就是在试图运行程序之前排除所有错误,但这是不现实的,余下的问题必须在运行期间得到解决。Java的异常分为两种,Checked异常和Runtime异常,Java认为Checked异常都是可以在编译阶段被处理的异常,所以强制程序处理所有的Checked异常,而Runtime异常无须处理。

Checked异常可以提醒程序员需要处理所有可能发生的异常,但Checked异常也给编程带来了一些繁琐之处。所以Checked异常也是Java领域一个备受争论的话题。

 

异常处理机制

Java的异常处理机制可以让程序具有极好的容错性,让程序更加健壮。当程序运行出现意外时,系统会自动生成一个Exception对象来通知程序,从而实现业务功能实现代码和错误处理代码的分离,提供更好的可读性。

使用try catch捕获异常

正如之前代码所提示的,希望有一种强大的if可以表示所有的错误情况,让程序可以一次性处理所有的错误,也就是希望将错误集中处理。

如果执行try块里的业务逻辑代码时出现异常,系统自动生成一个异常对象,该异常对象被提交给Java运行时环境。这个过程被称为抛出异常。

 

当Java运行 时环境受到异常对象时,会寻找能处理该异常块的catch块,如果找到适合的catch块就把异常交给catch块执行,这个过程称为捕获异常,如果Java运行时环境找不到捕获异常的catch块,则运行时环境终止,Java程序也将退出。

try块与if语句不一样,try块后的花括号不可以省略,即使try块里只有一行代码,也不可省略这个花括号。与之类似的是catch块也不可以省略花括号。

Java把所有非正常情况分成两种:异常和错误

都继承Throwable父类

Error错误一般指的是与虚拟机相关的问题,如系统崩溃、虚拟机错误、动态链接失败等。这种错误无法恢复或不可能捕获、将导致应用程序中断。

通常应用程序无法处理这些错误,因此应用程序不应该试图用catch块捕获error对象

 

一些常见的异常

IndexOutOfBoundsException  数组越界异常

NumberFormatException  数字格式异常

ArithmeticException  除0异常

 

异常捕获时一定要记住先捕获小异常再捕获大异常

在Java7之前,每个catch块只能捕获一种类型的异常,但从Java7开始,一个catch块可以捕获多种类型的异常。

捕获多种类型的异常时。多种异常类型之间用竖线 隔开

捕获多种类型的异常时,异常变量有隐式的final修饰,因此程序不能对异常变量重新赋值

 

 访问异常信息

如果程序需要在catch块中访问异常对象的相关信息,则可以通过访问catch块的异常形参来捕获。当Java运行时决定调用某个catch块来处理该异常对象时,会将异常对象赋值给catch后的异常参数,程序即可以通过该参数来获得异常的相关信息

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

getMessage:返回该异常的详细描述字符串

printStackTrace将该异常的跟踪栈信息输出到标准错误输出

getStackTrace返回该异常的跟踪栈信息

 

 

使用finally回收资源

有些时候,程序在try块里打开了一些物理资源(例如数据库、忘了连接和磁盘文件等)

这些物理资源必须显示回收

 

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

 

除非在try块。catch块中调用了退出虚拟机的方法,否则不管在try块、catch块中执行怎样的代码,出现怎样的情况,异常处理的finally块总会被执行。

在通常情况下,不要再finally块中使用return或throw等导致方法终止的语句

一旦finally中使用了return或throw语句,将会导致try块和catch块中的return和throw语句失效。

 

在Java7之前,必须要自己关闭资源,Java7增强了try语句的功能,它允许在try关键字后紧跟着一堆圆括号圆括号可以声明初始化一个或多个资源,此处的资源指的是那些必须在程序结束时,显式关闭的资源(比如数据库连接、网络连接等)

try语句在该语句结束时自动关闭了这些资源。

 

需要指出的是,为了保证try语句可以正常关闭资源,这些资源实现类必须实现AutoCloseable或者Closeable接口,实现这两个接口就必须实现close方法。

 

 Java7几乎将所有的资源类,包括文件IO的各种类,JDBC编程的Connection和Statement接口进行了改写,改写后资源类都实现了AutoCloseable或Closeable接口

 如果程序需要,自动关闭资源的try语句后也可以带多个catch块和一个finally块。

 

Java异常被分为两大类:Checked异常和Runtime异常(运行时异常)。所有的RuntimeException类以及其子类的实例被称为Runtime异常,不是RuntimeException类以及其子类的异常实例则被称为Checked异常。

只有Java语言提供了Checked异常,Checked异常体现了Java的严谨性,它要求程序员必须注意该异常,要么显式声明抛出,要么显式捕获并处理它,总之不允许对Checked异常不闻不问。这是一种非常严谨的设计哲学,可以增加程序的健壮性。

问题是大部分方法总是不能明确地知道如何处理异常,只能声明抛出该异常,而这种情况又是如此普遍,所以Checked异常降低了程序开发的生产率和代码的执行效率,关于Checked异常的优劣,在Java领域是一个备受争论的问题。

 

使用throws声明抛出异常的思想是,当前方法不知道如何处理这种类型的异常,该异常应该由上一级调用者处理,如果main方法也不知道如何处理这种类型的异常,也可以使用throws声明抛出异常,该异常将交给jvm处理。jvm对异常的处理方法是,打印异常的跟踪栈信息,并终止程序运行,这就是前面程序在遇到异常后自动结束的原因。

前面章节里有些程序已经用到了throws声明抛出,throws声明抛出只能在方法签名中使用,throws可以声明抛出多个异常类,多个异常类之间以逗号隔开。throws声明抛出的语法格式如下

 

 

 

 使用throws声明抛出异常时有一个限制,就是方法重写时两小的原则:子类方法声明抛出的异常类型应该是与父类方法声明抛出的异常类型的子类相同,子类方法声明抛出的异常类型不允许比父类方法抛出的异常多。

 

 

上面程序中Sub子类中的test方法声明抛出exception该Exception,该Exception是父类声明抛出异常IOException类的父类,浙江导致程序无法通过编译

由此可见,使用Checked异常至少存在如下两大不便之处。

对于程序中的checked异常,Java要求必须显式捕获并处理该异常,或者显式声明抛出该异常,这就增加了编程的复杂度。

如果在方法中显式声明抛出Checked异常,将会导致方法签名与异常耦合,如果该方法是重写父类的方法,则该方法抛出的异常还会受到被重写方法所抛出异常的限制。

在大部分使用推荐使用runtime异常,而不使用Checked异常。尤其当程序需要自行抛出异常时,使用Runtime异常更简洁

很多时候,系统是否抛出异常,可能需要根据应用的业务需要来决定,如果程序中的数据、执行与既定的业务需求不符,这就是一种异常。由于与业务需求不符而产生的异常,必须由程序员决定抛出,系统无法抛出这种异常。

如果需要在程序中自行抛出异常,则应该使用throw语句,throw语句可以单独使用,throw语句抛出的不是异常类,而是一个异常实例,而且每次只能抛出一个异常实例。

 

 如果throw语句抛出的是Checked异常,则该throw语句要么在try块里,显式捕获该异常,要么放在一个带throws声明抛出的方法中,即将该异常交给该方法的调用者。如果throw语句抛出的异常是Runtime异常,则该语句无需放在try块里,也无需放在带throws声明的方法中,程序即可以显式地使用try catch 来捕获并处理该异常,也可以完全不理会该异常,把该异常交给该方法调用者处理。

 

 通过上面程序也可以看出自行抛出Runtime异常比自行抛出Checked异常的灵活性更好。同样,抛出Checked异常则可以让编译器提醒程序员必须处理该异常。

 

自定义异常类

通常情况下,程序很少会自行抛出系统异常,因为异常的类名通常也包含了异常的有用信息。所以在选择抛出异常时,应该选择适合的异常类,从而可以明确的描述该异常的情况。在这种情况下,应用程序常常需要抛出自定义异常。

用户自定义异常都应该继承Exception基类,如果希望自定义Runtime异常,则应该继承RuntimeException基类。定义异常类时通常需要提供两个构造器,一个是无参数的构造器,另一个是带一个字符串的参数构造器,这个字符串将作为异常对象的描述信息(也就是getMessage方法返回值)

 

 上面程序创建了一个异常类,并为该异常类提供了两个构造器。

如果需要自定义Runtime异常,只需要将AuctionException程序中的Exception基类改成RuntimeException基类,其他地方无需修改

在大部分情况下,创建自定义异常都可以采用这种方法。

 

前面介绍的异常处理方式有如下两种

在出现异常的方法内捕获并处理异常,该方法的调用者将不能再次捕获该异常。

该方法签名中声明抛出该异常,将该异常完全交给方法调用者处理

 

在实际应用中往往需要更复杂的处理方式,当一个异常出现的时候,单单靠某个方法无法完全处理该异常,必须由几个方法协作才可以完全处理该异常。也就是说,在异常出现的当前方法中,程序只对异常进行部分处理,还有些处理需要在该方法的调用者中才能完成,所以应该再次抛出异常,让该方法的调用者捕获到异常。

为了实现这种通过多个方法协作处理同一个异常的情形,可以在catch块中结合throw语句来完成。如下例子程序示范了这种catch和throw同时使用的方法。

这种catch和throw结合使用的情况在大型企业级应用中非常有用,企业级应用对异常的处理通常分为两个部分:1、应用后台需要通过日志来记录异常发生的详细情况。2、应用还需要根据异常向应用使用者传达某种提示。在这种情形下,所有异常都需要两个方法共同完成,也就必须将catch和throw结合使用。

 

Java7增强的throw语句。

Java7增强的throw语句,在Java7以前,Java编译器处理:“简单而粗暴”  由于在捕获该异常时声明ex类型是Exception,因此Java编译器认为这段代码可能抛出Exception异常,所以包含这段代码的方法通常需要声明抛出Exception异常。

Java7开始,Java编译器会执行更细致的检查,Java编译器会检查throw语句抛出异常的实际类型。

 

异常链。

对于真实的企业级应用而言,常常有严格的分层关系,层与层之间有非常清晰的划分,这样上层功能的实现严格依赖于下层api也不会跨层访问。

对于正常用户而言,他们不想看到底层的sqlexception异常

对于恶意用户而言,会不安全。

 

posted @ 2020-02-06 17:23  chyblogs  阅读(198)  评论(0)    收藏  举报