`
SunnyYoona
  • 浏览: 372210 次
社区版块
存档分类
最新评论

[Java开发之路](5)异常详解

 
阅读更多
1. 异常分类

在Java程序设计语言中,异常对象都是派生于Throwable类的一个实例。其是如果Java中的异常类不能满足需求,用户可以创建自己的异常类。

下图是Java异常层次结构的一个简化示意图。



从图上可以看出,所有的异常都是继承于Throwable类,但是在下一层立即分解为两个分支:Error和Exception。

(1)Error

Error描述了Java运行时系统的内部错误资源耗尽错误。应用程序不应该抛出这种类型的错误,如果出现了这样的内部错误,除了通告用户,并尽力使程序安全的终止之外,再也无能为力了。这种情况很少见。大多数错误与代码编写者执行的操作无关,而表示代码运行时JVM出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。



这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,并且这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。

(2)Exception

程序设计者应该关注的是Exception,这一层次异常又分为两个分支:IOException和RuntimeException。划分这两个分支的规则是:由程序错误导致的异常属于RuntimeException;而程序本身没有问题,但是由于IO错误这类问题导致的异常属于IOException



派生于RuntimeException的异常包括:
  • 异常的运算条件,如一个整数除以0时
  • 错误的类型转换
  • 数组访问越界
  • 访问空指针
派生于IOException的异常包括:
  • 试图在文件尾部后面读取数据
  • 试图打开一个不存在的文件
如果出现RuntimeException异常则表明一定是你的问题”,这是一条相当有道理的规则。


注意:
异常和错误的区别:异常能被程序本身可以处理,错误是无法处理。

(3)Checked Exception 与 UnChecked Exception

Java语言规范将派生于Error类或者RuntimeException类的所有异常称为未检查异常(UnChecked 异常),所有其他的异常(包括IOException)称为已检查异常(Checked 异常)。编译器将核查是否为所有的Checked 异常提供了异常处理器。

2. 声明已检查异常

如果遇到了无法处理的情况,那么Java的方法可以抛出一个异常。这个道理很简单:一个方法不仅需要告诉编译器将要返回什么值,还要告诉编译器有可能发生什么错误。
例如:一段读取文件的代码知道优肯风读取的文件不存在,或者内容为空,因此,试图处理文件信息的代码就需要通知编译器可能会抛出IOException异常。

方法应该声明所有可能抛出的已检查异常,这样可以反映出这个方法可能抛出哪类已检查异常

例如:下面是标准类库中提供的FileInputStream类的一个构造方法的声明异常情况:
public FileInputStream(String name) throws FileNotFoundException
这个异常声明表示这个构造方法根据给定的字符串name正确情况下产生一个FileInputStream对象,但是也有可能抛出一个FileNotFoundException异常。如果抛出异常,方法不会初始化一个FileInputStream对象,而是抛出一个FileNotFoundException对象。抛出异常之后,运行时系统开始搜索异常处理器,以便知道如何处理FileNotFoundException对象。

不是所有可能抛出的异常都必须进行声明,以下4种情况时记得抛出异常:
  • 调用一个抛出已检查异常的方法,如FileInputStream构造方法
  • 程序运行过程中发现错误,并且利用throw语句抛出一个已检查异常
  • 程序出现错误,例如,a[-1]=0会抛出一个下标越界的未检查异常
  • Java虚拟机和运行库出现的内部错误

对于前两种情况必须进行声明。

不需要声明Java的内部错误,即从Error继承的错误,任何程序代码都具有抛出那些异常的潜能,但是它们在我们的控制范围之外。同样也不应该声明从RuntimeException继承的那些未检查异常。

void Read(int index) throws ArrayIndexOutOfBoundsException // bad style
{
...
}
这些运行时错误完全在我们的控制范围之内,如果特别关注数组下标引发的错误,就会将更多的时间花费在修正程序中的错误上,而不是说明这些错误发生的可能性上


总结:

  1. 一个方法必须声明所有可能抛出的已检查异常,而未检查异常要么不可控制(Error),要么就应该避免发生(RuntimeException)。
  2. 如果一个方法没有声明所有可能发生的已检查异常,编译器就会给出一个错误信息。
  3. 如果类中的一个方法声明会抛出一个异常,而这个异常是某个特定类的实例时,则这个方法就有可能抛出一个这个类,或者这个类一个子类的异常。


3. 如何抛出异常

假设有一个方法用来读取文件内容,给定的文本长度为1024,但是读到700个字符之后文件就结束了,我们认定这不是一种正常的情况,希望抛出异常。
首先决定应该抛出什么类型的异常(EOFException),知道之后抛出异常的语句如下:

// 第一种方法
throw new EOFException();
// 第二种方法
EOFException e = new EOFException();
throw e;

EOFException类还有一个含有一个字符串参数的构造方法,可以更加细致描述异常出现的状况:
String gripe = "未到指定长度,文件读取结束";
throw new EOFException(gripe);

对于一个已经存在的异常类,抛出异常过程:
  • 找到一个合适的异常类
  • 创建这个异常类的一个对象
  • 将对象抛出

4. 创建异常类

在程序中,可能会遇到任何标准程序类都没有能够充分描述清楚的问题,这种情况下,我们需要创建我们自己的异常类。我们需要做的就是定义一个派生类于Exception,或者派生于Exception子类的类。习惯上,定义的类应该包含两个构造器,一个是默认的构造器,一个是带有详细描述信息的构造器。
public class FileFormatException extends Exception{
/**
*
*/
private static final long serialVersionUID = 1L;
// 默认构造器
public FileFormatException(){
}
// 带有详细描述信息的构造器
public FileFormatException(String gripe){
super(gripe);
}
}
现在我们可以抛出我们自己定义的异常类型了。
throw new FileFormatException;

5. 捕获异常

如果某个异常发生的时候没有任何地方进行捕获,那程序就会终止,并在控制台上打印出异常信息,其中包括异常的类型和堆栈的内容。
package com.qunar.test;
public class ExceptionTest {
public static void main(String[] args) {
int a = 10;
int b = 0;
System.out.printf("%d / %d = %d",a,b,a/b);
System.out.println("测试结束...");
}
}

控制台信息:

Exceptioninthread"main"java.lang.ArithmeticException:/byzero
atcom.qunar.test.ExceptionTest.main(ExceptionTest.java:8)

从异常信息可以看出程序并没有运行完全,没有输出“测试结束...”,程序就终止,并且在控制台打印出异常信息。

要想捕获异常,必须使用try/catch语句块。
try{
code
more code
more code
}
catch (Exception e) {
handle for this type
}

(1)如果在try语句块中任何代码抛出一个在catch子句中说明的异常类,那么:
  • 程序将跳过try语句块的剩余代码
  • 程序将执行catch子句的处理器代码
(2)如果在try语句块中代码没有抛出异常,那么程序将跳过catch子句。
(3)如果方法中的任何代码抛出了一个在catch子句没有声明的异常类型,那么这个方法就会立刻退出。

package com.qunar.test;
public class ExceptionTest {
public static void main(String[] args) {
int a = 10;
int b = 0;
try{
System.out.printf("%d / %d = %d",a,b,a/b);
}
catch (ArithmeticException e) {
System.out.println("a / b b 不能等于0");
}
System.out.println("测试结束...");
}
}

运行结果:

a/bb不能等于0
测试结束...

看一个例子:(读取文本程序代码)
public void read(String name){
try{
InputStream inputStream = new FileInputStream(name);
int b;
while((b = inputStream.read()) != -1){
// ...
}//while
}
catch (IOException e) {
e.printStackTrace();
}
}
对于一个普通的程序来说,这样的处理异常基本上合乎情理,但是,通常最好的情况是什么也不做,而是将异常传递给调用者。如果read方法出现了错误,就让read方法的调用者去处理,如果采用这种处理方式,就必须声明这个方法可能会抛出一个IOException。

public void read(String name) throws IOException{
InputStream inputStream = new FileInputStream(name);
int b;
while((b = inputStream.read()) != -1){
// ...
}//while
}
如果调用了一个抛出已检查异常的方法,就必须对它进行处理或者将它继续进行传递。

出现了两种处理方式,那到底哪种方式更好呢?
通常,应该捕获那些知道如何处理的异常,而将那些不知道怎么处理的异常继续进行传递。如果想传递一个异常,就必须在方法添加一个throws说明符。仔细阅读Java API文档,以便知道每个方法可能会抛出哪种异常,然后再决定是自己处理,还是加到throws列表中。

6. 捕获多个异常

在一个try语句块中可以捕获多个异常类型,并对不同类型的异常做出不同的处理。
try{
}
catch (FileNotFoundException e) {
// emergency action for missing files
}
catch (UnknownException e) {
// emergency action for unknown hosts
}
catch (IOException e) {
// emergency action for all other I/O problems
}

7. 再次抛出异常与异常链

在catch子句中可以抛出一个异常,这样做的目的是改变异常的类型。如果开发了一个供其他程序员使用的子系统,那么用于表示系统故障的异常类型会有多种解释。ServletException就是这样一个异常类型的例子。执行Servlet的代码可能不想知道发生的错误的细节原因,但希望知道servlet是否有问题。

下面给出了捕获异常并将它再次抛出的基本方法:
try{
access the database
}
catch(SQLException e){// 发生错误的细节原因
throw new ServletException("database error:"+e.getMessage());// 只希望知道servlet是否有问题
}
再给出一个更好的方法:包装技术
try{
access the databse
}
catch(SQLException e){
Throwable se = new ServletException("database error");
// 设置初始异常
se.initCause(e);
throw se;
}
这个方法更好的地方在于当捕获到异常时可以使用下面语句得到原始异常(不会丢失原始异常的细节):
Throwable e = se.getCause();
这种方法还可以解决一下问题:
如果一个方法中发生了一个已检查异常,而不允许抛出它,那么包装技术就十分有用,我们可以捕获这个已检查异常,并把它包装成一个运行时异常。

8. finally子句

当代码抛出一个异常时,就会终止方法中剩余代码的处理,并退出这个方法的执行。如果方法获得了一些本地资源,并且只有这个方法知道,同时这些资源再退出之前必须回收。这就需要finally子句来解决。不管是否有异常,finally子句的代码都被执行。

举个例子,当发生异常时,恰当的关闭所有数据库的链接是非常重要的,这种情况就可以使用finally。
try{
}
catch (Exception e) {
}
finally{
}
(1)如果代码没有抛出异常。首先执行try语句块中的全部代码,然后执行finally子句中的代码。
(2)如果代码抛出异常,并且在catch子句可以捕获到。首先执行try语句块汇总的所有代码,直到发生异常为止,此时跳过try语句块中剩余代码,去执行与该异常匹配的catch子句中的代码,最后执行finally子句中的代码。
(3)如果代码抛出异常,但这个异常不是由catch子句捕获的。首先执行try语句块汇总的所有代码,直到发生异常为止,此时跳过try语句块中剩余代码,然后执行finally子句中的代码,并将异常抛给这个方法的调用者。

建议独立使用try/catch和try/finally语句块。这样可以提高代码的清晰度。例如:

package com.qunar.test;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class ExceptionTest {
public static void main(String[] args) {
String name = "";
InputStream in = null;
// 确保报告出现的错误
try{
in = new FileInputStream(name);
// 确保关闭输入流
try{
// code that might throw exceptions
}
finally{
in.close();
}
}
catch (IOException e) {
// show error message
}
}
}
内层的try语句块只有一个职责,就是确保关闭输入流。外层的try语句块也只有一个职责,就是确保报告出现的错误。这种设计不仅清楚,而且还具有一个功能,就是将会报告finally子句中出现的错误

finally子句包含return语句时,会出现意想不到的结果。假设利用return语句从try语句块中退出。在方法返回前,finally子句的内容将被执行。如果finally子句中也有一个return语句,这个返回值将会覆盖原始的返回值。
package com.qunar.test;
public class ExceptionTest {
public static int function(int n){
try{
int r = n * n;
return r;
}
finally{
if(n == 2){
return 0;
}//if
}//finally
}
public static void main(String[] args) {
System.out.println(function(2));
}
}
如果调用function(2),那么try语句块中计算结果为4,并执行return语句。但是在方法真正返回前,还要执行finally子句,将使得方法返回0,覆盖了原始的返回值4。

有时候,finally也会有麻烦。
InputStream in = ...;
try{
// code that might throw exceptions
}
finally{
in.close();
}
假设在try语句块抛出了一个非IOException的异常,这个异常只有方法的调用者才能处理。执行finally子句,并调用in.close()方法,而close方法本身也可能抛出IOException异常。当这种情况出现时,原始的异常将会丢失,转而抛出clsoe方法的异常。

分享到:
评论

相关推荐

    Java开发详解.zip

    010102_【第1章:JAVA概述及开发环境搭建】_Java开发环境搭建笔记.pdf 010201_【第2章:简单Java程序】_简单Java程序笔记.pdf 010301_【第3章:Java基础程序设计】_Java数据类型笔记.pdf 010302_【第3章:Java基础...

    Java应用开发详解

    对初学JAVA的人非常有帮助,对java的 多线程、集合类、IO、图形界面、网络编程、异常处理、数组复制与排序及位运算都有涉及。

    Java应用开发详解视频教程(22集)

    资源名称:Java应用开发详解视频教程(22集)资源目录:【】第00章:JAVA课堂序章【】第01章:JAVA概述及开发环境搭建【】第02章:简单Java程序【】第03章:Java基础程序设计【】第04章:数组与方法【】第05章:面向...

    JAVA SE应用详解.pdf

    Java语言具有面向对象、与平台无关、简单稳定、支持多线程等特点,已经成为当下最重要的网络编程语言之一。本书分为12个章节,主要包括Java语言概述 、基本语法规则、面向对象核心技术、数组和集合、异常处理、文件...

    JAVA WEB 开发详解:XML+XSLT+SERVLET+JSP 深入剖析与实例应用.part2

    此外,本书的配套光盘还免费提供了价值人民币330元的java教学视频,对java语言进行了全面讲解,帮助一些不会java语言的读者快速地从java基础知识的学习中过渡到java web的学习与开发上. 第1部分 xml篇. 第1章 xml...

    JAVA WEB 开发详解:XML+XSLT+SERVLET+JSP 深入剖析与实例应用.part3

    此外,本书的配套光盘还免费提供了价值人民币330元的java教学视频,对java语言进行了全面讲解,帮助一些不会java语言的读者快速地从java基础知识的学习中过渡到java web的学习与开发上. 第1部分 xml篇. 第1章 xml...

    JAVA WEB 开发详解:XML+XSLT+SERVLET+JSP 深入剖析与实例应用.part4

    此外,本书的配套光盘还免费提供了价值人民币330元的java教学视频,对java语言进行了全面讲解,帮助一些不会java语言的读者快速地从java基础知识的学习中过渡到java web的学习与开发上. 第1部分 xml篇. 第1章 xml...

    JAVA WEB 开发详解:XML+XSLT+SERVLET+JSP 深入剖析与实例应用.part5

    此外,本书的配套光盘还免费提供了价值人民币330元的java教学视频,对java语言进行了全面讲解,帮助一些不会java语言的读者快速地从java基础知识的学习中过渡到java web的学习与开发上. 第1部分 xml篇. 第1章 xml...

    java基础案例与开发详解案例源码全

    1.2.4 Java的竞争对手5 1.2.5 Java在应用领域的优势7 1.3 Java平台的体系结构7 1.3.1 JavaSE标准版8 1.3.2 JavaEE企业版10 1.3.3 JavaME微型版11 1.4 JavaSE环境安装和配置12 1.4.1 什么是JDK12 1.4.2 JDK安装目录和...

    java开发中遇到的异常汇总详解

    主要介绍了java开发中遇到的异常汇总详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

    java异常详解

    这个异常是很多原本在jb等开发环境中开发的程序员,把jb下的程序包放在wtk下编译经常出现的问题,异常的解释是"指定的类不存在",这里主要考虑一下类的名称和路径是否正确即可,如果是在jb下做的程序包,一般都是...

    Java程序设计PPT详解

    《Java程序设计》共分12章,内容包括Java语言概述、面向对象编程初步、Java的基本语法、类库与数组、面向对象编程深入、图形用户界面编程、异常处理和输入输出,以及多线程编程等内容。 本书讲解Java程序设计知识...

    Java开发手册(阿里巴巴).docx

    《Java 开发手册》是阿里巴巴集团技术团队的集体智慧结晶和经验总结,经历了多次大规模一 线实战的检验及不断完善,公开...《码出高效》和《Java开发手册》书籍版所得收入均捐赠公益事情,希望用技术情怀帮助更多的人。

    Java Bug模式详解

    Java.Bug模式详解 第1章 混乱环境下的灵活方法 1.1 软件设计、实现和维护的趋势 1.1.1 对于稳定、安全 系统的需求增加 1.1.2 传统软件工程技 术的局限性 1.1.3 开放源代码的软 件项目的可利用性 1.1.4 对于...

    《Java和Android开发实战详解》第6到10章源代码-by 南邮-陈杨

    10.1 Java的异常处理 191 10.1.1 异常处理的架构 191 10.1.2 Throwable类 191 10.1.3 异常处理语句 192 10.1.4 同时处理多种异常 194 10.2 抛出异常与自定义Exception类 196 10.2.1 使用throw关键字 ...

    JAVA 开发之用静态方法返回类名的实例详解

    主要介绍了JAVA 开发之用静态方法返回类名的实例详解的相关资料,这里主要说明使用异常来得到类名,希望能帮助到大家,需要的朋友可以参考下

    java高手真经 光盘源码

    javaexception.zip 07.Java面向对象编程扩展(计算器异常捕捉实例) 第3部分(5个程序包) javaio.zip 09.Java输入/输出流 javavi.zip 09.Java输入/输出流上机作业参考(文本编辑器) javathread.zip 10.Java多...

    J2EE应用开发详解

    第1章 Java Web应用开发简介 1 1.1 Java EE应用概述 1 1.2 Java EE概念 1 1.2.1 Java EE多层模型 1 1.2.2 Java EE体系结构 2 1.3 Java EE的核心API与组件 4 1.4 Web服务器和应用服务器 13 1.5 小结 16 第2章 建立...

Global site tag (gtag.js) - Google Analytics