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类。
异常方法
以下是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对象的栈跟踪,并添加到栈跟踪中的所有先前信息。 |
捕捉异常
使用try和catch关键字可以捕获异常,方法是在可能产生异常的代码周围放置了一个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。