Java异常Error和Exception
简述
程序运行时,发生了不被期望的结果,阻止了程序按照预期正常执行,这就是异常。世界上没有不出错的程序,只有正确处理好意外情况,才能保证程序的可靠性。
Java 语言在设计之初就提供了相对完善的异常处理机制,这也是 Java 得以大行其道的原因之一,因为这种机制大大降低了编写和维护可靠程序的门槛。如今,异常处理机制已经成为现代编程语言的标配。
异常处理机制能让程序在异常发生时,按照代码的预先设定的异常处理逻辑,针对性地处理异常,让程序尽最大可能恢复正常并继续执行,且保持代码的清晰。
Java中的异常可能是函数中的语句执行时引发的,也可能是程序员通过throw语句手动抛出的,只要Java程序中产生了异常,就需要用一个对应类型的异常对象来封装异常,JRE就会试图寻找相应的异常处理程序来处理异常。
Throwable类是Java异常类型的顶层父类,一个对象只有是 Throwable 类的实例,才是一个异常对象,才能被异常处理机制识别。JDK中内建了一些常用的异常类,我们也可以自定义异常
Java异常分类
Java标准库内置了一些通用的异常类,这些类以Throwable为顶层父类。
Throwable又派生出Error类和Exception类
错误: Error类以及它的子类的实例,代表了JVM本身的错误。错误不能被程序员通过代码处理,Error很少出现。因此,程序员应该关注Exception为父类的分支下的各种异常类。
异常: Exception以及它的子类,代表程序运行时发送的各种不期望发生的事件。可以被Java异常处理机制使用,是异常处理的核心。
总体上我们根据Javac对异常的处理要求,将异常类分为2类。
非检查性异常(unckecked exception):
Error 和 RuntimeException 以及子类。javac在编译时,不会提示和发现这样的异常,我们可以编写代码处理(使用try…catch…finally)这样的异常,也可以不处理。对于这些异常,我们应该修正代码,而不是去通过异常处理器处理 。这样的异常发生的原因多半是代码写的有问题。如除0错误ArithmeticException,错误的强制类型转换错误ClassCastException,数组索引越界ArrayIndexOutOfBoundsException,使用了空对象NullPointerException等等。
Java 根据各个类库也定义了一些其他的异常,下面的表中列出了 Java 的非检查性异常。
异常 | 描述 |
---|---|
ArithmeticException | 当出现异常的运算条件时,抛出此异常。例如,一个整数”除以零”时,抛出此类的一个实例。 |
ArrayIndexOutOfBoundsException | 用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引。 |
ArrayStoreException | 试图将错误类型的对象存储到一个对象数组时抛出的异常。 |
ClassCastException | 当试图将对象强制转换为不是实例的子类时,抛出该异常。 |
IllegalArgumentException | 抛出的异常表明向方法传递了一个不合法或不正确的参数。 |
IllegalMonitorStateException | 抛出的异常表明某一线程已经试图等待对象的监视器,或者试图通知其他正在等待对象的监视器而本身没有指定监视器的线程。 |
IllegalStateException | 在非法或不适当的时间调用方法时产生的信号。换句话说,即 Java 环境或 Java 应用程序没有处于请求操作所要求的适当状态下。 |
IllegalThreadStateException | 线程没有处于请求操作所要求的适当状态时抛出的异常。 |
IndexOutOfBoundsException | 指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。 |
NegativeArraySizeException | 如果应用程序试图创建大小为负的数组,则抛出该异常。 |
NullPointerException |
当应用程序试图在需要对象的地方使用 null 时,抛出该异常 |
NumberFormatException | 当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。 |
SecurityException | 由安全管理器抛出的异常,指示存在安全侵犯。 |
StringIndexOutOfBoundsException |
此异常由 String 方法抛出,指示索引或者为负,或者超出字符串的大小。 |
UnsupportedOperationException | 当不支持请求的操作时,抛出该异常。 |
下面的表中列出了 Java 定义在 java.lang 包中的检查性异常类。
异常 | 描述 |
---|---|
ClassNotFoundException | 应用程序试图加载类时,找不到相应的类,抛出该异常。 |
CloneNotSupportedException |
当调用 Object 类中的 clone 方法克隆对象,但该对象的类无法实现 Cloneable 接口时,抛出该异常。 |
IllegalAccessException | 拒绝访问一个类的时候,抛出该异常。 |
InstantiationException |
当试图使用 Class 类中的 newInstance 方法创建一个类的实例,而指定的类对象因为是一个接口或是一个抽象类而无法实例化时,抛出该异常。 |
InterruptedException | 一个线程被另一个线程中断,抛出该异常。 |
NoSuchFieldException | 请求的变量不存在 |
NoSuchMethodException | 请求的方法不存在 |
异常处理基本用法
在编写代码处理异常时,对于检查性异常,有2种不同的处理方式:
- 使用try{}catch{}finally语句块处理它
- 在函数签名中使用 throws 声明交给函数调用者去解决
try{}catch{}finally{} 用法
try{
//try块中放可能发生异常的代码。
//如果执行完try且不发生异常,则接着去执行finally块和finally后面的代码
//如果发生异常,则尝试去匹配catch块。
}catch(SQLException SQLexception){
//每一个catch块用于捕获并处理一个特定的异常,或者这异常类型的子类。Java7中可以将多个异常声明在一个catch中。
//catch后面的括号定义了异常类型和异常参数。如果异常与之匹配且是最先匹配到的,则虚拟机将使用这个catch块来处理异常。
//在catch块中可以使用这个块的异常参数来获取异常的相关信息。异常参数是这个catch块中的局部变量,其它块不能访问。
//如果当前try块中发生的异常在后续的所有catch中都没捕获到,则先去执行finally,然后到这个函数的外部caller中去匹配异常处理器。
//如果try中没有发生异常,则所有的catch块将被忽略。
}catch(Exception exception){
//...
}finally{
//finally块通常是可选的。
//无论异常是否发生,异常是否匹配被处理,finally都会执行。
//一个try至少要有一个catch块,否则, 至少要有1个finally块。但是finally不是用来处理异常的,finally不会捕获异常。
//finally主要做一些清理工作,如流的关闭,数据库连接的关闭等。
}
需要注意的点:
1、try块中的局部变量和catch块中的局部变量(包括异常变量),以及finally中的局部变量,它们之间不可共享使用。
2、每一个catch块用于处理一个异常。异常匹配是按照catch块的顺序从上往下寻找的,只有第一个匹配的catch会得到执行。匹配时,不仅运行精确匹配,也支持父类匹配,因此,如果同一个try块下的多个catch异常类型有父子关系,应该将子类异常放在前面,父类异常放在后面,这样保证每个catch块都有存在的意义。
3、java中,异常处理的任务就是将执行控制流从异常发生的地方转移到能够处理这种异常的地方去。也就是说:当一个函数的某条语句发生异常时,这条语句的后面的语句不会再执行,它失去了焦点。执行流跳转到最近的匹配的异常处理catch代码块去执行,异常被处理完后,执行流会在“这个异常的catch代码块”后面接着执行。
public static void main(String[] args){
try {
divideOperation();
}catch(ArithmeticException ae) {
System.out.println("异常捕获,处理异常");
}
System.out.println("我是catch块后边的程序,异常处理后,我继续执行");
}
public static void divideOperation(){
int a = 5/0; //异常抛出点
System.out.println("出现异常啦,方法被终止了,我躺着中枪了"); //不会执行
}
结果:
异常捕获,处理异常
我是catch块后边的程序,异常处理后,我继续执行
throws/throw 用法
如果一个方法没有捕获一个检查性异常,那么该方法必须使用 throws 关键字来声明。throws 关键字放在方法签名的尾部。
也可以使用 throw 关键字抛出一个异常,无论它是新实例化的还是刚捕获到的。
下面方法的声明抛出一个 RemoteException 异常:
public class ThrowsDemo
{
public void deposit(double amount) throws RemoteException
{
// 方法内容
throw new RemoteException();
}
}
一个方法可以声明抛出多个异常,多个异常之间用逗号隔开。
例如,下面的方法声明抛出 RemoteException 和 InsufficientFundsException:
public class ThrowsDemo2
{
public void withdraw(double amount) throws RemoteException,
InsufficientFundsException
{
//方法内容
}
}
finally关键字
finally 关键字用来创建在 try 代码块后面执行的代码块。
无论是否发生异常,finally 代码块中的代码总会被执行,通常用来做资源释放操作:关闭文件,关闭数据库连接等等。
1、不要在finally中抛出异常,finally块没有处理异常的能力,处理异常的只能是catch块。
2、在同一try…catch…finally块中 ,如果try中抛出异常,且有匹配的catch块,则先执行catch块,再执行finally块。如果没有catch块匹配,则先执行finally,然后去外面的调用者中寻找合适的catch块。
3、在同一try…catch…finally块中 ,try发生异常,且匹配的catch块中处理异常时也抛出异常,那么后面的finally也会执行:首先执行finally块,然后去外围调用者中寻找合适的catch块。
4、不要在fianlly中使用return,否则会覆盖返回值
5、减轻finally的任务,不要在finally中做一些其它的事情,finally块仅仅用来释放资源是最合适的。
6、尽量将所有的return写在函数的最后面,而不是try … catch … finally中。
自定义异常
在 Java 中你可以自定义异常,编写自己的异常类时需要记住下面的几点:
- 所有异常都必须是 Throwable 的子类。
- 如果希望写一个检查性异常类,则需要继承 Exception 类。
- 如果你想写一个运行时异常类,那么需要继承 RuntimeException 类。
可以像下面这样定义自己的异常类:
class MyException extends Exception{
}
只继承Exception 类来创建的异常类是检查性异常类。
下面的 InsufficientFundsException 类是用户定义的异常类,它继承自 Exception。
一个异常类和其它任何类一样,包含有变量和方法。
以下实例是一个银行账户的模拟,通过银行卡的号码完成识别,可以进行存钱和取钱的操作。
package com.lkf.basic;
//自定义异常类,继承Exception类
public class InsufficientFundsException extends Exception
{
//账户余额
private double amount;
public InsufficientFundsException(double amount)
{
this.amount = amount;
}
public double getAmount()
{
return amount;
}
}
为了展示如何使用我们自定义的异常类,
在下面的 CheckingAccount 类中包含一个 withdraw() 方法抛出一个 InsufficientFundsException 异常。
package com.lkf.basic;
//此类模拟银行账户
public class CheckingAccount {
//balance为余额,
private double balance;
//number为卡号
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() 方法。
package com.lkf.basic;
public class BankDemo {
public static void main(String[] args) {
CheckingAccount c = new CheckingAccount(101);
System.out.println("存钱 500 元....");
c.deposit(500.00);
try {
System.out.println("\n取钱 100 元...");
c.withdraw(100.00);
System.out.println("\n取钱 600 元...");
c.withdraw(600.00);
} catch (InsufficientFundsException e) {
System.out.println("很抱歉您的余额不足,差:"
+ e.getAmount());
e.printStackTrace();
}
}
}
结果:
存钱 500 元....
取钱 100 元...
取钱 600 元...
很抱歉您的余额不足,差:200.0
com.lkf.basic.InsufficientFundsException
at com.lkf.basic.CheckingAccount.withdraw(CheckingAccount.java:26)
at com.lkf.basic.BankDemo.main(BankDemo.java:12)