Java异常精讲

Java 异常处理

你将学到

  • Java中异常的捕获方法

  • throws/throwfinally 关键字

  • Java中常见的异常

  • Java中异常类层次结构

  • 常见的异常错误的使用方式

  • 自定义异常的方法


Java中异常的捕获方法


语法:

try {
   // 可能发生异常的程序代码
}catch(ExceptionName e) {
   //捕获到名为ExceptionName的异常的处理代码
}

解释: try 部分包裹着可能发生异常的代码,可以保护程序发生异常时不突然结束,而是被 catch 部分捕获到异常,从而让程序员有机会处理这个异常,保证程序继续走下去。

举例:

public class Main {
    public static void main(String[] args) {
        try {
            int[] data = new int[]{1,2,3};
            getDataByIndex(-1,data);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }

    }

    public static int getDataByIndex(int index,int[] data) {
        if(index<0||index>=data.length)
            throw new ArrayIndexOutOfBoundsException("数组下标越界");
        return data[index];
    }
}

在上面的例子中,当数组越界的时候方法getDataByIndex会抛出异常,因为我们在main方法中用try…catch包裹了这个方法的调用,所以当异常发生时,main方法中的catch就会捕获到这个异常,这里的处理方式是将异常打印到控制台(开发中debug阶段经常用这种方式来调试代码,但是,realse阶段需要将异常写入日志)。

throws/throw和 finally 关键字


throw 关键字
上例中我们已经在getDataByIndex方法中用到了throw关键字来抛出一个异常。

在写代码时,如果某些情况下不允许程序继续走下去,就可以像上面一样,抛出一个异常给调用者,以便调用者知道发生了异常,并对异常情况进行处理。

throws 关键字
上面的例子是主动抛出异常(throw表示直接抛出异常),但是某些情况下我们希望表达某个方法有可能发生异常,可以用throws关键字。

举例:

public void withdraw(double amount) throws RemoteException, InsufficientFundsException
{
    // Method implementation
}

表示withdraw方法可能抛出RemoteException或者InsufficientFundsException两种异常。如果调用这样一个方法,Java编译机会要求调用者添加try…catch的代码来捕获异常,而这里有可能发生两种异常,我们可以使用多重捕获块:

try{
   // 程序代码
}catch(异常类型1 异常的变量名1){
  // 程序代码
}catch(异常类型2 异常的变量名2){
  // 程序代码
}

finally 关键字

无论是否发生异常,都希望执行的代码,可以放在finally关键子块里:

语法:

try{
  // 程序代码
}catch(异常类型1 异常的变量名1){
  // 程序代码
}finally{
  // 无论是否发生异常,都会执行的程序代码
}

举例:

public class ExcepTest{
  public static void main(String args[]){
    int a[] = new int[2];
    try{
       System.out.println("Access element three :" + a[3]);
    }catch(ArrayIndexOutOfBoundsException e){
       System.out.println("Exception thrown  :" + e);
    }
    finally{
       a[0] = 6;
       System.out.println("First element value: " +a[0]);
       System.out.println("The finally statement is executed");
    }
  }
}

结果:
Exception thrown  :java.lang.ArrayIndexOutOfBoundsException: 3
First element value: 6
The finally statement is executed

Java中常见的异常


运行时异常
异常名 异常解析
ArrayIndexOutOfBoundException 数组索引越界异常
ArithmeticException 算术条件异常,如除数为0
NullPointException 空指针异常
ClassNotFoundException 找不到类异常
NegativeArraySizeException 数组长度为负异常
ArrayStoreException 数组中包含不兼容的值抛出异常
SecurityException 安全性异常
IllegalArgumentException 非法参数异常
IOException
异常名 异常解析
IOException 操作输入流和输出流可能出现的异常
EOFException 文件已结束异常
FileNotFoundException 文件未找到异常

常见的异常绝非仅此而已,具体的只能使用的时候碰到再具体分析。

Java中异常类层次结构


系统的异常类层次分明

java异常结构

虽然结构看着很多,但是需要程序员处理的一般只有Exception类型的异常。因为Error通常是灾难性的致命的错误,是程序无法控制和处理的,当出现这些异常时,Java虚拟机(JVM)一般会选择终止线程。

  • 运行时异常(非检查性异常):包括RuntimeException及其子类和Error。
  • 检查性异常:在编译时不能被简单地忽略,例如用户错误或问题引起的异常(如打开一个不存在文件时)。

异常错误的使用方式


选择异常

1、是否需要立即终止。

2、是否需要进一步处理和恢复。

举例:

将 SQLException 定义为非检测异常(就是不需要立即停止的异常),这样操作数据时开发人员理所当然的认为 SQLException 不需要调用代码的显式捕捉和处理,进而会导致严重的 Connection 不关闭、Transaction 不回滚、DB 中出现脏数据等情况。

所以需要将 SQLException 定义为检测异常,才会驱使开发人员去显式捕捉,并且在代码产生异常后清理资源。

对代码层次结构的污染

举例:

public Man retrieve(Long id) throw SQLException {
 //根据 ID 查询数据库
}

上面这段代码咋一看没什么问题,但是从设计耦合角度仔细考虑一下,这里的 SQLException 污染到了上层调用代码,调用层需要显式的利用 try-catch 捕捉,或者向更上层次进一步抛出。根据设计隔离原则,我们可以适当修改成:

public Man retrieve(Long id) {
     try{
            //根据 ID 查询数据库
     }catch(SQLException e){
            //利用非检测异常封装检测异常,降低层次耦合
            throw new RuntimeException(SQLErrorCode, e);
     }finally{
            //关闭连接,清理资源
     }
}

将异常包含在循环语句块中

for(int i=0; i<100; i++){
    try{
    }catch(XXXException e){
         //….
    }
}

这么做最主要的问题在于是处理异常消耗系统资源太多。

利用 Exception 捕捉所有潜在的异常

有时候懒得捕捉所有异常,就利用基类 Exception 捕捉所有潜在的异常:

public void retrieveObjectById(Long id){
    try{
        //…抛出 IOException 的代码调用
        //…抛出 SQLException 的代码调用
    }catch(Exception e){
        //这里利用基类 Exception 捕捉的所有潜在的异常,如果多个层次这样捕捉,会丢失原始异常的有效信息
        throw new RuntimeException(“Exception in retieveObjectById”, e);
    }
}

这么做的坏处是:丢失了发生其他异常的具体信息。

在你的方法里抛出不具体的检查性异常

public void foo() throws Exception { //错误方式
}

“早throw晚catch”原则

应该尽快抛出(throw)异常,并尽可能晚地捕获(catch)它。 你应该等到你有足够的信息来妥善处理它。

自定义异常的方法


  • 如果希望写一个检查性异常类,则需要继承 Exception 类。
  • 如果希望写一个运行时异常类,那么需要继承 RuntimeException 类。
class MyException extends Exception{
}

举例:

class MyException extends Exception {
    private int detail;
    MyException(int a){
        detail = a;
    }
    public String toString(){
        return "MyException ["+ detail + "]";
    }
}
public class TestMyException{
    static void compute(int a) throws MyException{
        System.out.println("Called compute(" + a + ")");
        if(a > 10){
            throw new MyException(a);
        }
        System.out.println("Normal exit!");
    }
    public static void main(String [] args){
        try{
            compute(1);
            compute(20);
        }catch(MyException me){
            System.out.println("Caught " + me);
        }
    }
}

运行结果:

Called compute(1)

Normal exit!

Called compute(20)

Caught MyException [20]

发表评论

关闭菜单