bugstac...

Java 之路 (十二) -- 异常(基本常识、try-catc

2018-08-13  本文已影响98人  whd_Alive

1. 初识异常

正式介绍异常之前,先来看一个例子:

public static void readFileByBytes(String fileName) {  
    // 一般先创建file对象  
    FileInputStream fileInput = null;  
    try {  
        File file = new File(fileName);  
        if (!file.exists()) {  
            file.createNewFile();  
        }  
        byte[] buffer = new byte[1024];  
        fileInput = new FileInputStream(file);  
        int byteread = 0;  
        // byteread表示一次读取到buffers中的数量。  
        while ((byteread = fileInput.read(buffer)) != -1) {  
            System.out.write(buffer, 0, byteread);  
        }  
  
    } catch (Exception e) {  
        // TODO: handle exception  
    } finally {  
  
        try {  
            if (fileInput != null) {  
                fileInput.close();  
            }  
        } catch (IOException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
    }  
}

上述代码展示了一种进行文件读取的方式,是一个运用 Java 异常处理的典型例子。

可能你会有疑问:try、catch、finally 是什么?为什么要写成这个格式?如果不这么写会发生什么情况?

让我们假设这样一个情况:假如在我调用语句 File file = new File(fileName); 创建 File时,由于某种原因未创建成功(比如内存不足,hhh),那么当我后面使用这个不存在的 file 时,就会发生错误,我们的程序会无法继续运行。这种情况下,我们就可以说该程序抛出了一个“异常”,try 就可以理解成“我需要尝试执行一下正常的流程,但是其中会出现某些错误”,之后 Java 会通过 catch 来对这个异常进行"捕获",描述发生某种错误时应该做些什么。

而当无论是否发生了错误,我都需要来做一些清理工作时,就需要使用 finally 语句。finally 中的语句无论如何最终都会执行,这样即便我的代码在中间某部分出错了,但是清理工作依旧可以执行。

上述知识一个基本的介绍,下面我们就一步步学习 Java 中的异常处理机制。


2. 异常的基本常识

2.1 异常和错误

首先需要理解一下关于异常和错误的关系:

2.2 异常的抛出

上面我们提到了 抛出异常,下面具体看看抛出异常时会发生哪些事。

2.3 Java 标准异常

Java 标准库中内建了一系列的异常,顶级父类为 Throwable,表示任何可以作为异常被抛出的类。

Throwable 可以被分为两种类型:

异常的结构图

图摘自 http://www.cnblogs.com/lulipro/p/7504267.html

声明一点:上面的结构是不完整的,但是在下太懒了,直接接用上述文章的图,感兴趣可以自行查看官方文档进行整理。

实际上,对于 Java 编译时是否对异常进行检查,Exception 又分为两类:

  1. 不受检查的异常:RuntimeException

    1. 这种异常属于错误,即便处理也无法使程序恢复,导致的原因通常是因为错误操作(比如使用 null 引用)或者程序员的疏忽(比如数组越界),这部分是由 Java 运行时检测处理,编译器不会检查,因此代码中对该类异常需要忽略。

      自动被 Java 虚拟机抛出,自动进行捕获。

  2. 被检查的异常:除了 RuntimeException 及其子类 以外的 Exception 子类

    1. 程序员处理的实际上就是这部分异常,编译时会被强制检查。

2.4 自定义异常

通常异常的名称代表发生的问题,并且异常的名称应该可以望文知义

如果需要自定义异常,必须继承已有的异常类,最好是选择意思相近的异常类继承,使名字做到望名生义。

按照国际惯例,自定义的异常应该总是包含如下的构造函数:

  • 一个无参构造函数
  • 一个带有String参数的构造函数,并传递给父类的构造函数。
  • 一个带有String参数和Throwable参数,并都传递给父类构造函数
  • 一个带有Throwable 参数的构造函数,并传递给父类的构造函数。

下面是IOException类的完整源代码,可以借鉴。

public class IOException extends Exception
{
    static final long serialVersionUID = 7818375828146090155L;

    public IOException()
    {
        super();
    }

    public IOException(String message)
    {
        super(message);
    }

    public IOException(String message, Throwable cause)
    {
        super(message, cause);
    }

    
    public IOException(Throwable cause)
    {
        super(cause);
    }
}

2.5 异常的意义


3. 异常处理机制

对于异常处理,有两种方式

如下:

void f() throws 潜在异常列表{//throws 表示可能有一些异常我无法处理,于是向上级抛出
    //try-catch-finally 处理当前信息足以解决的异常
    try{
        //可能抛出异常的方法调用
    }catch(SomeException se) {
        //必备
        //异常处理
    }finally{
        //可选
        //一些清理工作
    }
}

一些基础:

3.1 捕获异常

try - catch

终止模型 & 恢复模型

重新抛出异常:在 catch 中捕获异常后,得到了对当前异常对象的引用,此时可以直接把它重新抛出。

3.1.2 异常匹配

3.1.3 finally 进行清理

用于把除了内存之外的资源恢复到其初始状态。

finally 子句总能执行

3.2 抛出异常

3.2.1 异常抛出

throw

3.2.2 异常链

异常链:在捕获一个异常后抛出另一个异常,并且把原始异常的的信息保存下来。

3.2.3 异常声明

如果一个方法内部的代码会抛出检查异常(checked exception),而方法自己又没有完全处理掉,则 javac 保证你必须在方法的签名上使用 throws 关键字声明这些可能抛出的异常,否则编译不通过。

它属于方法声明的一部分,紧跟在形参列表之后,使用关键字 throws + 潜在异常类型的列表,仅仅是将函数中可能出现的异常向调用者声明,而自己则不具体处理。

void f() throws 潜在异常列表{
    //...
}

3.3 注意事项

覆盖方法的时候,只能抛出在基类方法的异常说明里列出的那些异常。

派生类构造器的异常说明必须包括基类构造器的异常说明。

派生类构造器不能捕获基类构造器抛出的异常。

对于在构造阶段可能会抛出异常,并且要求清理的类,最安全的使用方式时使用嵌套的 try 语句。

异常处理机制的好处:


4. 异常使用指南

  1. 在恰当的级别处理问题(在知道该如何处理的情况下才捕获异常)。
  2. 解决问题并且重新调用产生异常的方法。
  3. 进行少许修补,然后绕过异常发生的地方继续执行。
  4. 用别的数据进行计算,以代替方法预计会返回的值。
  5. 把当前运行环境下能做的事情尽量做完,然后把相同的异常重抛到更高层。
  6. 把当前运行环境下能做的事情尽量做完,然后把不同的异常抛到更高层。
  7. 终止程序。
  8. 进行简化(如果你的异常模式使问题变得太复杂,那用起来会非常痛苦也很烦人)。
  9. 让类库和程序更安全(这既是在为调试做短期投资,也是在为程序的健壮性做长期投资)。
上一篇 下一篇

猜你喜欢

热点阅读