Java提供了异常处理机制来帮助程序开发者检查和捕捉异常,提高程序的可读性和可维护性。Java中将异常封装在一个类中,出现错误时,会抛出异常。异常处理机制可以提前设置异常处理办法,当异常发生的时候程序自动处理不会直接终止,从而保持程序正常运行。异常的抛出分为运行时引发和使用throw手动抛出两种情况,只要产生异常,该异常就会被对应类型的异常对象封装起来,然后JRE再试图寻找解决异常的程序来处理异常。
说到异常,我们需要了解异常的顶层父类Throwable,一个对象只有一个Throwable类的实例,它才是一个异常对象,才有可能被异常处理机制识别。在JDK中,内建了一些常用的异常类,我们也可以自定义异常类。
一、异常处理机制
1、异常处理机制的作用
帮助程序开发者检查和捕捉异常,提高程序的可读性和可维护性
2、异常处理机制的工作原理
-
Java中将异常封装在一个类中,当出现异常使会抛出异常
-
异常处理机制可以提前设置异常处理办法
-
当运行时引发或使用throw手动抛出异常时
-
异常就会被对应类型的异常对象封装起来
-
JRE再试图寻找解决异常的程序来处理异常
3、异常产生原用
- 非法数据输入
- 文件不存在
- 网络中断或者内存溢出
二、顶层异常类
1、Throwable
一个对象只有一个Throwable类的实例,它才是一个异常对象,才有可能被异常处理机制识别。在JDK中,内建了一些常用的异常类,我们也可以自定义异常类。
2、Throwable子接口
(1)Exception
- 程序运行中的问题
- 能够被提前检查到的问题 ----检查异常
- IOException
- 不能被提前检查到的问题 ----非检查异常
- RuntimeException
- 能够被提前检查到的问题 ----检查异常
(2)Error
-
导致程序中断没有被运行的问题 ----非检查异常
- 问题无法处理
- VirtulMachineError
- AWTError
- 问题无法处理
三、Java内置异常类
Java 语言定义了一些异常类在 java.lang 标准包中。
标准运行时异常类的子类是最常见的异常类。由于 java.lang 包是默认加载到所有的 Java 程序的,所以大部分从运行时异常类继承而来的异常都可以直接使用。
1、常见的非检查异常
异常 | 描述 |
---|---|
ArithmeticException | 当出现异常的运算条件时,抛出此异常。例如,一个整数"除以零"时,抛出此类的一个实例。 |
ArrayIndexOutOfBoundsException | 用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引。 |
ArrayStoreException | 试图将错误类型的对象存储到一个对象数组时抛出的异常。 |
ClassCastException | 当试图将对象强制转换为不是实例的子类时,抛出该异常。 |
IllegalArgumentException | 抛出的异常表明向方法传递了一个不合法或不正确的参数。 |
IllegalMonitorStateException | 抛出的异常表明某一线程已经试图等待对象的监视器,或者试图通知其他正在等待对象的监视器而本身没有指定监视器的线程。 |
IllegalStateException | 在非法或不适当的时间调用方法时产生的信号。换句话说,即 Java 环境或 Java 应用程序没有处于请求操作所要求的适当状态下。 |
IllegalThreadStateException | 线程没有处于请求操作所要求的适当状态时抛出的异常。 |
IndexOutOfBoundsException | 指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。 |
NegativeArraySizeException | 如果应用程序试图创建大小为负的数组,则抛出该异常。 |
NullPointerException | 当应用程序试图在需要对象的地方使用 null 时,抛出该异常 |
NumberFormatException | 当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。 |
SecurityException | 由安全管理器抛出的异常,指示存在安全侵犯。 |
StringIndexOutOfBoundsException | 此异常由 String 方法抛出,指示索引或者为负,或者超出字符串的大小。 |
UnsupportedOperationException | 当不支持请求的操作时,抛出该异常。 |
2、常见的检查异常
异常 | 描述 |
---|---|
ClassNotFoundException | 应用程序试图加载类时,找不到相应的类,抛出该异常。 |
CloneNotSupportedException | 当调用 Object 类中的 clone 方法克隆对象,但该对象的类无法实现 Cloneable 接口时,抛出该异常。 |
IllegalAccessException | 拒绝访问一个类的时候,抛出该异常。 |
InstantiationException | 当试图使用 Class 类中的 newInstance 方法创建一个类的实例,而指定的类对象因为是一个接口或是一个抽象类而无法实例化时,抛出该异常。 |
InterruptedException | 一个线程被另一个线程中断,抛出该异常。 |
NoSuchFieldException | 请求的变量不存在 |
NoSuchMethodException | 请求的方法不存在 |
3、通用异常
在Java中定义了两种类型的异常和错误。
-
JVM(Java虚拟机)异常:由 JVM 抛出的异常或错误。例如:NullPointerException 类,ArrayIndexOutOfBoundsException 类,ClassCastException 类。
-
程序级异常:由程序或者API程序抛出的异常。例如 IllegalArgumentException 类,IllegalStateException 类。
四、异常方法
Throwable 类提供了一些异常处理方法:
方法 | 说明 |
---|---|
public String getMessage() | 返回关于发生的异常的详细信息。这个消息在 Throwable 类的构造函数中初始化了。 |
public Throwable getCause() | 返回一个Throwable 对象代表异常原因。 |
public String toString() | 使用getMessage()的结果返回类的串级名字。 |
public void printStackTrace() | 打印toString()结果和栈层次到System.err,即错 误输出流。 |
public StackTraceElement [] getStackTrace() | 返回一个包含堆栈层次的数组。下标为0的元素代 表栈顶,最后一个元素代表方法调用堆栈的栈底。 |
public Throwable fillInStackTrace() | 用当前的调用栈层次填充Throwable 对象栈次, 添加到栈层次任何先前信息中。 |
五、异常的处理
1、处理原则
(1)检查异常
- 一般是外部异常,这种异常是强制我们catch或throw的异常。你遇到这种异常必须进行catch或throw,如果不处理,编译器会报错。
- 举例
- IOException:例如试图从文件尾后读取数据等,这并不是程序本身的错误,而是在应用环境中出现的外部错误。
(2)非检查异常
- 不受检查的异常,一般是程序员的错误。运行时异我们不需要处理,完全由虚拟机接管。我们在写程序时不会进行catch或throw。
- 举例
- RuntimeException:包括错误的类型转换、数组越界访问和试图访问空指针等等,一般是程序员的错误,可以通过检查数组下标和数组边界来避免数组越界访问异常。
2、捕捉处理异常
(1)try-catch代码
-
try:用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出。
-
catch:用于捕获异常。catch用来捕获try语句块中发生的异常。
1)捕获异常
使用 try 和 catch 关键字可以捕获异常。try/catch 代码块放在异常可能发生的地方。
try/catch代码块中的代码称为保护代码,使用 try/catch 的语法如下:
try
{
// 程序代码
}catch(ExceptionName e1)
{
//Catch 块
}
Catch 语句包含要捕获异常类型的声明。当保护代码块中发生一个异常时,try 后面的 catch 块就会被检查。
如果发生的异常包含在 catch 块中,异常会被传递到该 catch 块,这和传递一个参数到方法是一样。
2)多重捕获
一个 try 代码块后面跟随多个 catch 代码块的情况就叫多重捕获。
多重捕获块的语法如下所示:
try{
// 程序代码
}catch(异常类型1 异常的变量名1){
// 程序代码
}catch(异常类型2 异常的变量名2){
// 程序代码
}catch(异常类型2 异常的变量名2){
// 程序代码
}
-
可以在 try 语句后面添加任意数量的 catch 块。
-
如果保护代码中发生异常,异常被抛给第一个 catch 块。
-
如果抛出异常的数据类型与 ExceptionType1 匹配,它在这里就会被捕获。
-
如果不匹配,它会被传递给第二个 catch 块。
-
直到异常被捕获或者通过所有的 catch 块。
(2)finally代码块
finally语句块总是会被执行。它主要用于回收在try块里打开的物力资源(如数据库连接、网络连接和磁盘文件)。只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。
try{
// 程序代码
}catch(异常类型1 异常的变量名1){
// 程序代码
}catch(异常类型2 异常的变量名2){
// 程序代码
}finally{
// 程序代码
}
-
finally 关键字用来创建在 try 代码块后面执行的代码块。
-
无论是否发生异常,finally 代码块中的代码总会被执行。
-
在 finally 代码块中,可以运行清理类型等收尾善后性质的语句。
-
finally 代码块出现在 catch 代码块最后,语法如下:
(3)捕获异常注意事项:
- catch 不能独立于 try 存在。
- 在 try/catch 后面添加 finally 块并非强制性要求的。
- try 代码后不能既没 catch 块也没 finally 块。
- try, catch, finally 块之间不能添加任何代码。
3、在方法中抛出异常
(1)throws
用于抛出异常,无论它是新实例化的还是刚捕获到的。
(2)throw
用在方法签名中,用于声明该方法可能抛出的异常。主方法上也可以使用throws抛出。如果在主方法上使用了throws抛出,就表示在主方法里面可以不用强制性进行异常处理,如果出现了异常,就交给JVM进行默认处理,则此时会导致程序中断执行。
(3)throw和throws的区别
- throws用在方法声明后,表示抛出异常,由方法的调用者处理,而throw用在方法体内,用来构造一个异常,由方法体的语句处理。
- throws是声明这个方法会抛出这种类型的额异常,以便于使用它的调用者知道要捕捉这个异常,而throw是直接抛出一个异常实例。
- throws表示的是出现异常的一种可能性,并不一定会产生这些异常,如果使用throw,就一定会产生某种异常。
(4)声明抛出一个或多个异常
1)声明抛出一个异常
声明抛出一个 RemoteException 异常:
import java.io.*;
public class className
{
public void deposit(double amount) throws RemoteException
{
// Method implementation
throw new RemoteException();
}
//Remainder of class definition
}
2)声明抛出多个异常
一个方法可以声明抛出多个异常,多个异常之间用逗号隔开。
例如,下面的方法声明抛出 RemoteException 和 InsufficientFundsException:
import java.io.*;
public class className
{
public void withdraw(double amount) throws RemoteException,
InsufficientFundsException
{
// Method implementation
}
//Remainder of class definition
}
4、声明自定义异常
在 Java 中你可以自定义异常,语法如下:
class MyException extends Exception{
}
- 所有异常都必须是 Throwable 的子类。
- 如果希望写一个检查性异常类,则需要继承 Exception 类。
- 如果你想写一个运行时异常类,那么需要继承 RuntimeException 类。
- 一个异常类和其它任何类一样,包含有变量和方法。
六、实例
实例1:try-catch代码块捕捉异常
创建Tomato类,计算顾客购买一定数量的西红柿需要支付的金额,在主方法中使用tr-catch代码块将可能出现的异常进行异常处理。
public class Tomato {
public static void main(String[] args) {
// TODO Auto-generated method stub
try { //把可能产生的异常代码放在try中
String massage= "tomato:2.98/500g";
String[] strArr = massage.split(":");
String unitPriceStr = strArr[2].substring(0,4);
double weight = 650;
double unitPriceDou= Double.parseDouble(unitPriceStr);
System.out.println(massage+" buy "+weight+"g tomato,need pay "+(float)(weight/500+unitPriceDou)+" yuan!");
}
catch (Exception e) { //捕捉与已产生的异常类型相匹配的异常对象
// TODO: handle exception
e.printStackTrace(); //打印异常信息
}
System.out.println("Over"); //输出提示信息
}
}
Console:
java.lang.ArrayIndexOutOfBoundsException: Index 2 out of bounds for length 2
at Tomato.main(Tomato.java:9)
Over
⚠️注意:Exception是try代码块传递给catch代码块的异常类型,e是与产生的异常类型相匹配的异常对象
根据提示修改第九行代码为:
String unitPriceStr = strArr[1].substring(0,4);
得到我们想要的结果:
Console:
tomato:2.98/500g buy 650.0g tomato,need pay 4.28 yuan!
Over
⚠️注意:不要忽略catch代码块,否则try代码块中出现的异常很难被找到,毕竟已经在捕捉代码块异常了,出现异常之后就会优先在try代码块外寻找,却忽略了异常就在try代码块中,是因为catch代码块未编写异常处理程序导致异常被捕捉却没有被处理。这样这个try-catch程序就成了一个摆设。
实例2:try-catch异常多重捕获
修改实例1捕捉ArrayIndexOutOfBoundsException异常,以及除此之外其他所有异常.
public class Tomato {
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
String massage= "tomato:2.98/500g";
String[] strArr = massage.split(":");
String unitPriceStr = strArr[2].substring(0,4);
double weight = 650;
double unitPriceDou= Double.parseDouble(unitPriceStr);
System.out.println(massage+" buy "+weight+"g tomato,need pay "+(float)(weight/500+unitPriceDou)+" yuan!");
}
catch(ArrayIndexOutOfBoundsException mm){
mm.printStackTrace();
}
catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
System.out.println("Over");
}
}
⚠️注意:将捕捉Exception的catch代码块和捕捉ArrayIndexOutOfBoundsException异常的代码块换位置的话会报错,因为使用多个catch代码块时,父类(Exception)应该被放在子类(ArrayIndexOutOfBoundsException)的后面,否在就会报错。catch多重捕捉的原则是先子类后父类
实例3:finally代码块的使用
修改实例2的Tomato类,实现控制台输入单价的功能,在finally代码块中关闭输入对象。
public class Tomato {
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner scanner = new Scanner(System.in); //控制台输入
System.out.println("Today,tomato price is(x.xx): ");
String dayPrice = scanner.next();
if(dayPrice.length() == 4) { //输入的价格长度需要是4
try {
String massage= "tomato price:";
String[] strArr = massage.split(":");
String unitPriceStr = strArr[2].substring(0,4);
double weight = 650;
double unitPriceDou= Double.parseDouble(unitPriceStr);
System.out.println(massage+" buy "+weight+"g tomato,need pay "+(float)(weight/500+unitPriceDou)+" yuan!");
}
catch(ArrayIndexOutOfBoundsException mm){
mm.printStackTrace();
}
catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
finally {
scanner.close();
System.out.println("控制台输入对象被关闭"); //输出提示信息
}
}
else {
System.out.println("输入格式不正确,请按照x.xx的格式输入价格!");
}
}
}
Console:
Today,tomato price is(x.xx):
3.33
java.lang.ArrayIndexOutOfBoundsException: Index 2 out of bounds for length 1
控制台输入对象被关闭
at Tomato.main(Tomato.java:13)
程序在捕捉完一次行之后,会执行finally代码块中的代码。另外在以下三种情况下,finally代码块不会被执行:
- 在finally代码块中产生了异常
- 在前面的代码块中使用了
System.exit()
退出程序 - 程序所在的线程死亡
实例4:捕捉控制台输入异常
修改实例3,在Tomato类中创建支付方法pay(String dayPrice,double weight),在支付方法中抛出ArrayIndexOutOfBountsException异常。
import java.util.Scanner;
public class Tomato {
public void pay(String dayPrice,double weight) throws ArrayIndexOutOfBoundsException{
String massage= "tomato price:"+dayPrice+ " yuan!/500g";
String[] strArr = massage.split(":");
String unitPriceStr = strArr[2].substring(0,4);
double unitPriceDou= Double.parseDouble(unitPriceStr);
System.out.println(massage+" buy "+weight+"g tomato,need pay "+(float)(weight/500+unitPriceDou)+" yuan!");
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner scanner = new Scanner(System.in); //控制台输入
System.out.println("Today,tomato price is(x.xx): ");
String dayPrice = scanner.next();
if(dayPrice.length() == 4) { //输入的价格长度需要是4
double weight = 650;
try {
Tomato tomato = new Tomato();
tomato.pay(dayPrice, weight);
}
catch (ArrayIndexOutOfBoundsException mm){
//mm.printStackTrace();
System.out.println("pay方法抛出数组元素下标越界异常!");
}
catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
finally {
scanner.close();
System.out.println("控制台输入对象被关闭"); //输出提示信息
}
}
else {
System.out.println("输入格式不正确,请按照x.xx的格式输入价格!");
}
}
}
Console:
Today,tomato price is(x.xx):
2.35
pay方法抛出数组元素下标越界异常!
控制台输入对象被关闭
实例5:throw抛出异常
当商品的价格过高的时候,国家就会对这种商品采取宏观调控,使得该商品的额价格趋于稳定。规定西红柿单价不得超过7元,如果超过7元上限,就抛出异常。
- 创建PriceException父类,继承Exception父类
class PriceException extends Exception {
public PriceException(String massage) {
super(massage);
}
}
- 创建Tomato2子类,继承PriceException
import java.util.Scanner;
public class Tomato2 {
private double price;
public double getPrice() {
return price;
}
public void setPrice(double price) throws PriceException{
if(price>7.00) {
throw new PriceException("超过国家规定的最高单价!!!");
}
else {
this.price = price;
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner sc = new Scanner(System.in); //控制台输入
System.out.println("Today,tomato price is(x.xx): ");
String dayPrice = sc.next();
if(dayPrice.length() == 4) { //输入的价格长度需要是4
double unitPriceDou= Double.parseDouble(dayPrice);
Tomato2 tomato = new Tomato2();
try {
tomato.setPrice(unitPriceDou);
}
catch (Exception e) {
// TODO: handle exception
System.out.println(e.getMessage());
}
finally {
sc.close();
}
}
else {
System.out.println("输入格式不正确,请按照x.xx的格式输入价格!");
}
}
}
Console:
Today,tomato price is(x.xx):
8.29
超过国家规定的最高单价!!!
七、小结
1、异常的使用原则
- 不要过度使用,使用异常增加程序的健壮性的同时会降低程序的执行效率。
- 不要使用过于庞大的try-catch快,try块中业务过于复杂会增加异常原因分析难度。
- 避免使用catch(Exception e),如果所有异常都采用相同处理方式,将无法对异常进行分类。