异常的介绍

一(异常的定义):

针对程序中的非正常的代码,Java语言中引入了异常(Exception),以异常类的形式对这些非正常的代码进行封装,并通过异常处理机制对程序运行时发生的各种问题进行处理。

接下来通过一个案例认识什么是异常。

package 异常;

public class Demo1 {
    public static void main(String[] args) {
        int result=divide(6,0);
        System.out.println(result);
    }
    public static int divide (int x,int y){
        int result=x/y;
        return result;
    }
}

从运行结果可以看出,该程序发生了异常,显示"java.lang.ArithmeticException: / by zero"(被0除的算术运算异常)的错误信息。该异常发生后,程序会立即结束,无法继续向下执行。

ArithmeticException异常只是Java异常类中的一种,在Java中还提供了大量的异常类,这些类都继承自java.lang.Throwable类。接下来通过一张图来展示Throwable类的继承体系,如下图所示:

从图可以看出,Throwable有两个直接子类Error和Exception,其中Error代表程序中产生的错误,Exception代表程序中产生的异常。接下来就对这两个子类进行解释说明。

Error类称为错误类,它表示Java运行时产生的系统内部错误或资源耗尽的错误,是比较严重的,仅靠修改程序本身是不能恢复执行的,如系统崩溃,虚拟机错误等。

Exception类称为异常类 ,它表示程序本身可以处理的错误。在Java程序中开发的异常处理,都是针对Exception类及其子类的。在Exception类的众多子类中有一个RuntimeException类,该类及子类用于表示运行时异常。除了此类,Exception类下的其他子类都用于表示编译时异常。

二(异常的类型): 

在实际开发中,经常会在程序编译时期产生一些异常,而这些异常必须要进行处理,这种异常被称为编译时期异常,也称为checked异常。还有一种异常是在程序运行时期产生的,这种异常即使不编写异常处理代码,依然可以通过编译,称为运行时异常,也称为unchecked异常。接下来分别对这两种异常进行详细讲解。

1.编译时异常

在Exception的子类中,除了RuntimeException类及其子类外,其他子类都是编译时异常。编译时异常的特点是在程序过程中,Java编译器会对编写的代码进行检查,如果出现比较明显的异常就必须对异常进行处理,否则程序无法通过编译。处理编译时异常有2种方式,具体如下:

A:使用try……catch语句对异常进行捕获处理。

B:使用throws关键字声明抛出异常,让调用者对其处理。

2.运行时异常

RuntimeException类及其子类都是运行时异常。运行时异常是在程序运行时由Java虚拟机自动进行捕获异常,即使没有使用try……catch语句捕获或使用throws关键字声明抛出异常,程序也能通过编译,只是在运行时中可能报错。在Java中,常见的运行时异常有很多,常见的异常如下表所示: 

常见的运行时异常
异常类名称异常类说明
ArithmeticException算术异常
IndexOutOfBoundsException角标越界异常
ClassCastException类型转换异常
NullPointerException空指针异常
NumberFormatException数字格式化异常

运行时异常一般是由程序中的逻辑错误引起的,在程序运行时无法恢复。例如通过数组的角标访问数组的的元素时,如果超过了数组角标的最大值,就会发生角标越界异常,代码如下所示:

int[ ] arr=new int[5];
System.out.println(arr[5]);

上面的代码中,由于数组arr的长度为5,最大角标为4,当使用arr[5]访问数组的元素时就会发生数组角标越界的异常。

三(try……catch和finally):

当程序发生异常时,会立即终止,无法继续向下执行。为了保证程序能够有效的执行,Java中提供了一种对异常进行处理的方式———异常捕获。异常捕获通常使用try……catch语句,其具体语法格式如下:

try{
         //可能发生的异常
}catch(Exception类或其子类){
        //对捕获的异常进行相应的处理
}

上述代码中,try{ }语句块中包含的是可能发生异常的语句,catch(){ }代码块中编写针对捕获的异常进行处理的代码。当try{ }语句块中的程序发生了异常,系统会将这个异常的信息封装成一个异常对象,并将这个对象传递给catch(){ }代码块。catch(){ }代码块需要一个参数指明它所能接收的异常类型,这个参数的类型必须是Exception类或子类。

接下来使用try……catch语句对之前出现的异常进行捕获和处理。

package 异常;

public class Demo1 {
    public static void main(String[] args) {
        int result=divide(6,0);
        if (result==-1){
            System.out.println("程序发生异常!!!");
        }else{
            System.out.println(result);
        }

    }
    public static int divide (int x,int y){
        try{
            int result=x/y;
            return result;
        }catch (Exception e){
            System.out.println("捕获的异常信息为:"+e.getMessage());
        }
        return -1;
    }
}

该案例中,在定义的整数除法运算方法divide()中对可能发生异常的代码中用try……catch语句进行了捕获处理。在try{ }代码块中发生被0除异常,程序会转而执行catch(){}中的代码,通过调用Exception对象的getMessage()方法,即可返回异常信息“/by zero”。catch(){}代码块中对异常处理完毕后,程序仍会向下执行,而不会因为异常而终止运行。

需要注意的是:1.在try代码块中发生异常,语句后面的代码是不会被执行的, 该例中的return result;就没有执行。2.catch中的多个异常没有父子关系,则谁在上谁在下无所谓,catch中的多个异常有父子关系,则子类异常要写在父类异常的上面(子类异常准确度高),否则报错。

在程序中,有时候希望有些语句无论程序是否发生异常都要执行,这时就可以在try……catch语句后,加一个finally{ }代码块。接下来对该例进行修改,演示finally{ }语句块的用法。

package 异常;

public class Demo1 {
    public static void main(String[] args) {
        int result=divide(6,0);
        if (result==-1){
            System.out.println("程序发生异常!!!");
        }else{
            System.out.println(result);
        }

    }
    public static int divide (int x,int y){
        try{
            int result=x/y;
            return result;
        }catch (Exception e){
            System.out.println("捕获的异常信息为:"+e.getMessage());
        }finally {
            System.out.println("执行finally语句块,无论程序是否异常,都会执行");
        }
        return -1;
    }
}

 

在divide()方法添加了一个finally语句块,用于处理无论程序是否发生异常都要执行的语句,该代码不受return语句和程序异常的影响。正是由于这种特殊性,在程序设计时,经常会在try……catch后使用finally{}代码块来完成必须做的事情,例如释放系统资源,关闭线程池等。

需要注意的是,finally中的代码在一种情况下是不会执行的,那就是在try……catch中执行了System.exit(0)语句。System.exit(0)表示退出当前的Java虚拟机,Java虚拟机停止了,任何代码都不再执行了。

四(throws关键字):

有些时候,方法中的代码是否出现异常,开发者并不明确或不急于处理,为此,Java允许将这种异常从当前方法抛出,然后让后续的调用者在使用时在进行异常处理。在Java中,将异常抛出需要使用throws关键字来实现,该关键字用在会抛出异常的方法名称后,同时支持一次性抛出多种类型的异常,基本语法格式如下:

[修饰符]返回值类型 方法名([参数类型 参数名1……]) throws 异常类1,异常类2,……{
            //方法体……
}

从上述语法可以看出,throws关键字需要写在方法声明的后面,throws后面需要声明方法中发生的异常类型,通常将这种做法称为方法声明抛出一个异常。接下来对案例进行修改,在devide()方法上声明抛出异常。

 在调用devide()方法时,由于该方法声明抛出了异常,所以调用者在调用divide()方法时就必须进行处理,否择就会发生编译错误。从截图可以看出,IDEA在程序编译时发生了“Unhandled exception:java.lang.Exception”(未处理的异常类型)的问题,并且给出了2种解决方法。

 其中“Add exception to method signature”表示在方法上继续使用throws关键字进行抛出异常,而“Surround with try/catch”表示在出现异常的代码中使用try……catch代码块进行捕获处理。

下面对案例再进行修改,在调用divide()方法时对其进行try……catch捕获处理。

package 异常;

public class Demo2 {
    public static  int divide(int x,int y)throws Exception{
        int result=x/y;
        return result;
    }
    public static void main(String[] args) {
        int result= 0;
        try {
            result = divide(6,0);
            System.out.println(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
       
    }
}

由于使用了try……catch对divided()方法抛出的异常进行了异常捕获处理,所以程序可以通过编译通过,并正常执行。

需要注意的是,当调用者在调用有抛出异常的方法时,除了可以在调用程序中直接进行try……catch异常处理外,也可以根据提示使用throws关键字继续将异常抛出,这样程序也能通过编译。但是程序发生了异常,终究是需要处理的,如果没有被处理,程序就会非正常终止。

package 异常;

public class Demo2 {
    public static  int divide(int x,int y)throws Exception{
        int result=x/y;
        return result;
    }
    public static void main(String[] args) throws Exception {
        int result=divide(6,0);
        System.out.println(result);
    }
}

在调用divide()方法时,并没有对异常进行处理而是继续使用throws关键字将异常抛出。从运行结果可以看出,程序虽然通过了编译,但在运行时由于没有对"/by zero"的异常进行处理,最终导致程序终止运行。

五(throw关键字) :

除了可以通过throws关键字抛出异常外,还可以使用throw关键字抛出异常。与throws关键字不同的是,throw用于方法体内,并且抛出的是一个异常类对象,而throws关键字用在方法声明中,用来指明方法可能抛出的多个异常。

通过throw关键字抛出异常后,还需要使用throws关键字或try……catch对异常进行处理。需要注意的是,如果throw抛出的是Error、RuntimeException或它们的子类异常对象,则无需使用throws关键字或try……catch对异常进行处理。

使用throw关键字抛出异常的语法格式如下:

[修饰符]返回值类型 方法名 ([参数类型  参数名,……]) throws 抛出的异常类{
    //方法体……
    throw new Exception类或其子类构造风方法;
}

接下来通过一个案例来演示throw关键字的使用。

package 异常;

public class ThrowDemo1 {
    public static void printAge(int age) throws Exception {
        if (age<=0){
            throw new Exception("输入的年龄有误,必须为正整数!");
        }else {
            System.out.println("年龄为:"+age);
        }
    }
    public static void main(String[] args) {
        int age=-1;
        try{
            printAge(age);
        }catch (Exception e){
            System.out.println("捕获的异常信息为"+e.getMessage());
        }
    }
}

该例中,printAge()方法对输入的年龄进行了逻辑判断,虽然输入负数在语法上能通过编译,并且程序能够正常运行,但这显然与显示情况不符合,因此需要在方法中对输入的内容进行判断,当数值小于或等于0时,使用throw关键字抛出异常,并指定异常提示信息,同时在方法后继续用throws关键字处理抛出的异常。

 六(自定义异常):

Java中定义了大量的异常类,虽然这些异常类可以描述编程时出现的大部分异常情况,但是在程序开发中有时可能需要描述程序中特定的异常情况,例如在设计divide()方法时不允许除数为0,为了解决这样的问题,Java允许用户自定义异常类,但自定义的异常类必须继承至Exception或其子类。

接下来通过一个案例来学习自定义异常的创建。

package 异常;

public class DivideByMinusException extends Exception{
    public DivideByMinusException(){

    }
    public DivideByMinusException(String str){
        super(str);
    }
}
class Tset{
    public static int divide(int x,int y) throws DivideByMinusException {
        if(y==0){
            throw new DivideByMinusException("除数是0");
        }
        int result=x/y;
        return result;
    }

    public static void main(String[] args) {
        try {
            int result=divide(6,0);
            System.out.println(result);
        } catch (DivideByMinusException e) {
            System.out.println("捕获的异常信息为:"+e.getMessage());
        }

    }
}

该例中,divide()方法通过逻辑判断对除法运算的除数是否为0进行了判断,如果除数为0就使用throw关键字抛出自定义的DivideByMinusException异常对象,然后通过throws关键字抛出对象,并在最后通过try……catch语句捕获异常。从运行结果可以看出,程序执行后判断出除数为0,抛出了指定的异常信息。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值