Java异常


异常是在程序执行期间出现的问题,当一个异常发生时,程序的正常流程会中断并且程序会异常终止,因此要处理这些异常。

多种不同原因会导致异常发生,以下是发生异常的一些情况:

  • 用户输入了无效的数据;

  • 打开不存在的文件;

  • 通信过程中网络连接丢失,或者JVM内存不足。

这些异常中的某些是由用户错误引起的,一些是由程序员错误引起的,而其他则是由发生故障的物理资源引起的。

基于这些,我们分为三类,你需要了解它们,才能了解Java中异常处理的工作方式:

  • 编译时异常(受检查异常):编译时异常是编译器在编译时检查到的异常,这些异常不能被忽略,程序员应该处理这些异常。

例如,如果使用FileReader类从不存在的文件中读取数据,就会抛出 FileNotFoundException 异常,编译器提示程序员处理该异常。

import java.io.File;
import java.io.FileReader;

public class FilenotFound_Demo {

    public static void main(String args[]) {
        File file = new File("E://file.txt“);
        FileReader fr = new FileReader(file);
    }
}

如果你尝试编译上述程序,则会出现以下异常。

C:\>javac FilenotFound_Demo.java
FilenotFound_Demo.java:8: 错误: 未报告的异常错误FileNotFoundException; 必须对其进行捕获或声明以便抛出
        FileReader fr = new FileReader(file);
                        ^
1 个错误

注意:FileReader类的read()和close()方法可能会抛出IOException,因此编译器会通知处理IOException以及FileNotFoundException。

  • 运行时异常(不受检查异常):运行时异常是在执行时发生的异常,包括编程错误,例如逻辑错误或API使用不当,编译时将忽略运行时异常。

例如,如果在程序中声明了大小为5的数组,并尝试访问索引为6的元素,这时会发生 ArrayIndexOutOfBoundsExceptionException 异常。

public class Unchecked_Demo {
   
    public static void main(String args[]) {
        int num[] = {1, 2, 3, 4};
        System.out.println(num[5]);
    }
}

如果编译并执行上述程序,则会出现以下异常。

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 5
	at Exceptions.Unchecked_Demo.main(Unchecked_Demo.java:8)
  • 错误(Errors):这些不是异常,而是超出用户或程序员控制范围而产生的问题。错误通常会在代码中被忽略,因为你几乎无法对错误做任何事情,例如如果发生栈溢出,编译时也将忽略它们。

异常层次结构


所有异常类都是java.lang.Exception类的子类型,而Exception类是Throwable类的子类。除了Exceptions类,还有另一个名为Error的子类,它也是从Throwable类派生的。

Errors是发生严重故障时发生的异常情况,例如JVM内存不足,这些错误不会由Java程序处理。通常程序无法从错误中恢复。

Exception类具有两个主要的子类:IOException类和RuntimeException类。

exception.jpg

异常方法


以下是Throwable类中可用的重要方法的列表。

序号方法与说明
1

public String getMessage()

返回异常的详细消息,该消息在Throwable构造函数中初始化。

2

public Throwable getCause()

返回由Throwable对象表示的异常原因。

3

public String toString()

返回与getMessage()的结果关联的类名称。

4

public void printStackTrace()

将toString()的结果与堆栈跟踪一起输出到错误输出流System.err。

5

public StackTraceElement [] getStackTrace()

返回一个包含堆栈跟踪中每个元素的数组,索引0处的元素表示调用栈的顶部,而数组中的最后一个元素表示调用栈底部的方法。

6

public Throwable fillInStackTrace()

使用当前栈跟踪填充此Throwable对象的栈跟踪,并添加到栈跟踪中的所有先前信息。

捕捉异常


使用trycatch关键字可以捕获异常,方法是在可能产生异常的代码周围放置了一个try / catch块,语法如下所示:

语法

try {
    //受保护的代码
} catch (ExceptionName e1) {
    //捕获块
}

用try块将可能出现异常的代码括起来,发生异常时,由与其关联的catch块处理。

catch语句声明要捕获的异常类型,如果受保护的代码中发生异常,则依次检查catch块声明的异常类型。

如果当前catch块中匹配了发生的异常类型,则将异常以参数传递的方式传递给当前catch块处理。

以下程序声明了2个元素的数组,然后代码尝试访问索引为3的元素,并处理出现的异常。

//文件名:ExcepTest.java
import java.io.*;

public class ExcepTest {

    public static void main(String args[]) {
        try {
            int a[] = new int[2];
            System.out.println("Access element three :" + a[3]);
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("Exception thrown  :" + e);
        }
        System.out.println("Out of the block");
    }
}

这将产生以下结果:

Exception thrown  :java.lang.ArrayIndexOutOfBoundsException: 3
Out of the block

多个捕获块


一个try块可以有多个catch块,语法如下所示:

语法

try {
    //受保护的代码
} catch (ExceptionType1 e1) {
    //捕获块
} catch (ExceptionType2 e2) {
    //捕获块
} catch (ExceptionType3 e3) {
    //捕获块
}

前面的代码仅出现了三个catch块,其实一个try后可以有任意多个catch块。

如果在受保护的代码中发生异常,则将该异常从上到下测试catch块的异常类型。如果抛出的异常的数据类型与ExceptionType1相匹配,它将在此处被捕获。如果不是,则异常传递到第二个catch语句。这种情况一直持续到捕获到异常或通过所有捕获异常为止,在这种情况下,当前方法停止执行,并且异常被丢弃到调用栈中的上一个方法中。

这是介绍如何使用多个try / catch语句的代码段:

try {
    file = new FileInputStream(fileName);
    x = (byte) file.read();
} catch (IOException i) {
    i.printStackTrace();
    return -1;
} catch (FileNotFoundException f) // 无效! {
    f.printStackTrace();
    return -1;
}

捕获多种类型的异常


从Java7开始,可以使用单个catch块处理多个异常,此功能可以简化代码:

catch (IOException|FileNotFoundException ex) {
    logger.log(ex);
    throw ex;

Throws/Throw 关键字


如果一个方法不处理编译时异常,该方法必须使用throws关键字来声明它,throws关键字出现在一个方法的签名的最后。

你可以通过使用throw关键字来抛出一个异常,无论是一个新实例化的异常还是一个你刚刚捕获的异常。

试着理解throws和throw关键字之间的区别,throws是用来推迟处理一个编译时异常,而throw是用来明确抛出一个异常。

以下代码展示如何抛出RemoteException:

import java.io.*;
public class className {

    public void deposit(double amount) throws RemoteException {
        //方法实现
        throw new RemoteException();
    }
    //剩余的类定义
}

一个方法可以抛出多个异常,在这种情况下,多个异常用逗号分隔。例如以下方法抛出RemoteException和InsufficientFundsException:

import java.io.*;
public class className {

    public void withdraw(double amount) throws RemoteException,
        InsufficientFundsException {
        //方法实现
    }
    //剩余的类定义
}

finally代码块


finally块位于try块或catch块之后,无论是否发生异常,始终都会执行finally代码块中的代码。

通过使用finally块,无论受保护的代码发生了什么,都可以运行要执行的任何清理语句。

finally出现在catch块的末尾,其语法如下:

语法

try {
    //受保护的代码
} catch (ExceptionType1 e1) {
    //捕获块
} catch (ExceptionType2 e2) {
    //捕获块
} catch (ExceptionType3 e3) {
    //捕获块
}finally {
    // 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

注意以下几点:

  • 没有try语句,就不能有catch子句;

  • finally子句是可选的;

  • 没有catch子句或finally子句,try块将没有意思;

  • 在try、catch和finally块之间不能存在任何代码。

try-with-resources


当我们使用任何资源(例如流、连接等)时,我们必须使用finally块显式关闭它们。

在以下程序中,使用FileReader从文件中读取数据,然后在finally块将其关闭。

import java.io.File;
import java.io.FileReader;
import java.io.IOException;

public class ReadData_Demo {

    public static void main(String args[]) {
        FileReader fr = null;
        try {
            File file = new File("file.txt");
            fr = new FileReader(file); char [] a = new char[50];
            fr.read(a);   //将内容读取到数组
            for(char c : a)
            System.out.print(c);   //逐一打印字符
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                fr.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }
}

try-with-resources,也称为自动资源管理,是Java7中引入的新异常处理机制,该机制会自动关闭try catch块中使用的资源。

要使用此语句,只需要在括号内声明所需的资源,并且所创建的资源将在代码块末尾自动关闭。以下是try-with-resources语句的语法。

语法

try(FileReader fr = new FileReader("file path")) {
    //使用资源
    } catch () {
        //捕获体
    }
}

以下是使用try-with-resources语句读取文件中数据的程序。

import java.io.FileReader;
import java.io.IOException;

public class Try_withDemo {

    public static void main(String args[]) {
        try(FileReader fr = new FileReader("E://file.txt“)){
            char [] a = new char[50];
            fr.read(a);   //将内容读取到数组
            for(char c : a)
            System.out.print(c);   //逐一打印字符
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

使用try-with-resources语句时,请牢记以下几点。

  • 要使用带有try-with-resources语句的类,它应该实现AutoCloseable接口的close()方法,它会在运行时自动调用;

  • 可以在try-with-resources语句中声明多个类;

  • 当在try-with-resources语句的try块中声明多个类时,这些类将以相反的顺序关闭;

  • 除了括号内的资源声明外,其他所有内容均与try块的常规try / catch块相同;

  • 在try块开始之前,实例化在try中声明的资源;

  • 在try块中声明的资源被隐式声明为final。

用户定义的异常


你可以使用Java创建自己的异常,在编写自己的异常类时,请牢记以下几点:

  • 所有异常都必须是Throwable的子类;

  • 如果要编写由Handle或Delare Rule自动执行的编译时异常,则需要扩展Exception类;

  • 如果要编写运行时异常,则需要扩展RuntimeException类。

只需要继承预定义的Exception类就可以创建自定义的Exception类,语法如下所示:

class MyException extends Exception {
}

以下InsufficientFundsException类是一个用户定义的异常,它继承了Exception类并使其成为编译时异常。

//文件名InsufficientFundsException.java
import java.io.*;

public class InsufficientFundsException extends Exception {
    private double amount;
   
    public InsufficientFundsException(double amount) {
        this.amount = amount;
    }
   
    public double getAmount() {
        return amount;
    }
}

为了演示如何使用用户定义的异常,下面的CheckingAccount类包含一个抛出InsufficientFundsException的withdraw()方法。

//文件名CheckingAccount.java
import java.io.*;

public class CheckingAccount {
    private double balance;
    private int number;
   
    public CheckingAccount(int number) {
        this.number = number;
    }
   
    public void deposit(double amount) {
        balance += amount;
    }
   
    public void withdraw(double amount) throws InsufficientFundsException {
        if(amount <= balance) {
            balance -= amount;
        }else {
            double needs = amount - balance;
            throw new InsufficientFundsException(needs);
        }
    }
   
    public double getBalance() {
        return balance;
    }
   
    public int getNumber() {
        return number;
    }
}

以下BankDemo程序演示了如何调用CheckingAccount的deposit()和withdraw()方法。

//文件名BankDemo.java
public class BankDemo {

    public static void main(String [] args) {
        CheckingAccount c = new CheckingAccount(101);
        System.out.println("Depositing $500...");
        c.deposit(500.00);
      
        try {
            System.out.println("\nWithdrawing $100...");
            c.withdraw(100.00);
            System.out.println("\nWithdrawing $600...");
            c.withdraw(600.00);
        } catch (InsufficientFundsException e) {
            System.out.println("Sorry, but you are short $" + e.getAmount());
            e.printStackTrace();
        }
    }
}

编译上述三个文件,然后运行BankDemo。这将产生以下结果:

Depositing $500...

Withdrawing $100...

Withdrawing $600...
Sorry, but you are short $200.0
InsufficientFundsException
            at CheckingAccount.withdraw(CheckingAccount.java:25)
            at BankDemo.main(BankDemo.java:13)

常见异常


在Java中,可以定义两种类型的异常和错误:

  • JVM异常:这些是JVM抛出的异常/错误,例如NullPointerException、ArrayIndexOutOfBoundsException、ClassCastException。

  • 程序异常:这些异常是由应用程序或这程序员明确抛出的,例如IllegalArgumentException、IllegalStateException。