Java中的异常处理

异常概述

Java的基本设计思想是“Badly formed code will not be run!”。这句话的大致意思是:错误形式的代码不会被运行。 我们在写代码的时候,提升错误恢复能力是提升代码健壮的重要措施。而“为了创建一个更加健壮的系统,那么每一个组件都必须是健壮的”。从而,在Java中出现了异常处理机制。 不像C语言,基本处理错误的代码都是程序员写上去的,而在Java中,除非是要自己自定义异常的时候,我们一般都是通过异常处理代码块来解决问题的。不但提高了代码的健壮性,还提高了代码的可读性。 那么,异常处理的定义是什么呢?当程序运行时出现了异常(不是错误),可能是空指针异常等等很多异常,能够对当前出现异常的代码进行处理,或是直接报告异常,或是将异常抛给特定的位置进行决断处理。 同大多数的需求一样,异常处理也被设计者设计成了一个类:Throwable。在这个类的下面,又有Error(错误)、和Exception(异常)。Error(错误)一般情况下不会通过代码进行处理,因为一般能报错误的情况,都是十分严重的情况,大多数错误都是由JVM(Java虚拟机)引起的。例如下面的代码:

byte[] buf = new byte[1024*1024*1024];
System.out.println(buf);

执行这段代码,肯定是会报错的。原因如下: JVM默认情况下只管理了64M的内存,而我们的程序需要的是1G的内存,很显然已经超出了管理范围(内存溢出),所以会报错。 在Exception(异常)类下,又有RunTimException(运行时异常)以及费运行时异常。这里的“……类下”指的是继承关系。 下面,我会逐个类介绍,并且会附上相应的代码供大家参考。

Throwable类

基本方法 1. toString() 输出该异常的类名 2. getMessage() 输出异常的信息,需要通过构造方法传入异常信息,也就是new一个对象的时候传入的参数(手动滑稽) 3. printStackTrace() 打印栈信息。 代码片段如下:

Throwable able = new Throwable("恶心!!!");
System.out.println(able.toString()); // 输出该异常的类名
System.out.println(able.getMessage()); // 输出异常的信息
able.printStackTrace(); // 打印栈信息

运行结果如下:

那么,我们来看一个出现异常的例子: 这个函数传入的参数的y值可能是0,程序会出现异常并停止

public static void div(int x, int y) {
System.out.println(x / y);
System.out.println("除法运算");
}

那么对于这种情况,我们应该如何进行处理呢?这就正式引入了我们要讨论的话题,异常处理的方式。首先我们来介绍第一种。

1.try{//可能发生异常的代码 }catch(异常类 变量名){//处理}。

我们以上面那个除法运算函数作为最基本的例子。当我们没有进行异常处理的时候,程序遇到问题时会停止。进行了异常处理时,程序还会继续执行,并且会按照我们给出的格式进行报错。

try{
    div(10,0);
}catch(Throwable t){
        System.out.println(t.toString());
        System.out.println(t.getMessage());
        t.printStackTrace();
        System.out.println();
        System.out.println("除数不能为0");
}

程序运行的结果如下图所示:

那么,一个异常的处理解决了,我们该如何进行多个异常的处理呢?

多个异常的处理 为了实现多个异常的处理情况,这里我们使用最简单的方法:设定一个数组。代码如下:

public class Exception {
    public static void main(String[] args) {
        int[] arr = new int[] {1,2};
        div(1,0,arr);
    }
    public static void div(int i,int j,int arr[]) {
        try {
            System.out.println(arr[1]);
            System.out.println(i / j);
        }catch(ArithmeticException e) {
            e.toString();
            e.getMessage();
            e.printStackTrace();
            System.out.println("******算术异常*******");
        }catch(ArrayIndexOutOfBoundsException e) {
            e.toString();
            e.getMessage();
            e.printStackTrace();
            System.out.println("******数组角标越界*******");
        }catch(NullPointerException e) {
            e.toString();
            e.getMessage();
            e.printStackTrace();
            System.out.println("******空指针异常*******");
        }
    }
}

所以,当执行上述代码的时候,将会出现算数异常,也就是ArithmeticException。 如果将main函数中的代码换成这个样子:

public void main(String[] args){
    int[] arr = new int[] {1,2};
    arr = null;
    div(1,0,arr);
}

这时将会出现空指针异常,也就是NullPointerException ,原因很简单,我们已经将arr数组置为null,所以访问的时候肯定是会出现空指针异常的。 如果我们再将div中的代码改成下面这个样子:

System.out.println(arr[3]);

数组中一共有两个值,0、1,我们访问的是3,很显然是数组角标越界,也就是ArrayIndexOutOfBoundsException 。 总结 1. 程序中可能有多个语句发生异常,可以同时放在try中。如果某条语句发生异常的时候,程序将会对catch中的异常进行匹配,如果能够匹配上,则执行相应的catch中的代码,如果没有匹配上,程序停止。 2. 如果程序中真的出现了多个异常,则只会执行try代码片段中的第一个出现异常的语句的异常处理语句,剩余的异常不会再处理。 使用多态进行异常处理 什么是多态呢?我们之前肯定学过,简单来讲,就是“用父类的引用指向子类对象”,我简单解释一下,看下面的代码:

Father  f = new Son();

在这里,Son类是继承与Father类的,所以用Father的引用f来指向Son类的对象就是这个意思了,如果还不明白的话,就再好好看看前面的内容。 具体操作代码如下:

try {
        System.out.println(arr[1]); // 数组越界
        System.out.println(x / y); // 除零
        Son s = (Son) f; // 类型转换
    } catch (Exception e) {
        e.toString();
        e.getMessage();
        e.printStackTrace();
        System.out.println("出现错误");
    }

上面的代码很简单,就是说当无论出现什么错误的时候,都只用一个Exception来匹配,我们知道,其实各种各样的异常都是继承于Exception类的,所以可以用Exception的引用指向具体的异常对象,如NullPointerExeception等。 多个catch语句之间的执行顺序 1. 按照顺序执行,从上到下 2. 如果catch的异常有继承关系,则:当子类异常在上,父类异常在下的时候,编译正常。也就是说,如果NullPointerException在上,Exception在下的时候,满足这一条;当子类异常在下,父类异常在上的时候,比如NullPointerException在下,Exception在上,那么就会编译报错,因为子类异常无法catch到。所以,多个异常需要使用子类父类的顺序进行使用。 注意:处理异常应该catch异常具体的子类,可以处理的更具体,不要为了简化代码使用异常的父类。

抛出处理

除了try……catch这种方法进行异常处理外,我们还可以使用抛出处理异常。 看下面的这个方法:

public static void div(int x, int y) throws Exception { // 声明异常,通知方法调用者。
    if (y == 0) {
        throw new Exception("除数为0"); // throw关键字后面接受的是具体的异常的对象
    }
    System.out.println(x / y);
    System.out.println("除法运算");
}

上面的例子就是抛出处理,使用throw关键字,注意,在方法中使用的是throw,而在方法名上使用的是throws关键字,这个很好理解,有点类似于英文中的复数,一个方法中抛出的异常很有可能不止一个,所以使用throws方法。

public static void main(String[] args) {
        try {
            div(2, 0);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("over");
    }

调用了div方法后,我们需要对这个方法抛出的异常进行处理,这又涉及到了try……catch块。如果不对抛出的异常进行处理,那么编译不会通过。在main函数上依然可以即系抛出,这样就是交给了JVM进行处理了,肯定是不推荐这样做的。 throw和throws的区别 1. 相同:都是用于做异常的抛出处理的。 2. 不同点: 使用的位置: throws 使用在函数上,throw使用在函数内 后面接受的内容的个数不同: throws 后跟的是异常类,可以跟多个,用逗号隔开。 throw 后跟异常对象。

自定义异常

当现有异常体系中的异常无法满足我们的需求的时候,我们就需要自定义异常。 根据上面的介绍,我们知道,所有的异常都是继承于父类Exception,所以,我们自定义异常也是继承Exception就可以了。

class NoRiceException extends Exception {……}

这个Exception是什么意思呢? 字面意思就是没有米饭的异常,嘿嘿。 我们去食堂买饭,就说要买米饭(Rice),然后卖饭的阿姨告诉你,米饭卖没了,这就是NoRiceException,没有米饭的异常,这个异常在现有的异常体系中肯定是没有的。 下面再来介绍一个大头:

运行时异常和非运行时异常

  1. RuntimeException(运行时异常) RuntimeException的子类有:
    • ClassCastException————–多态中,可以使用Instanceof 判断,进行规避
    • ArithmeticException————–进行if判断,如果除数为0,进行return
    • NullPointerException————-进行if判断,是否为null
    • ArrayIndexOutOfBoundsException————使用数组length属性,避免越界 这些异常时可以通过程序员的良好编程习惯进行避免的
  2. 非运行时异常(受检异常) 如果出现了非运行时异常必须进行处理throw或者try{}catch(){}处理,否则编译器报错。
    • IOException 使用要导入包import java.io.IOException;
    • ClassNotFoundException
    • Sun 的API文档中的函数上声明异常,那么该异常是非运行是异常, 调用者必须处理。
    • 自定义异常一般情况下声明为非运行时异常

总结 1. 子类覆盖父类方法时,父类方法抛出异常,子类的覆盖方法可以不抛出异常,或者抛出父类方法的异常,或者该父类方法异常的子类。 2. 父类方法抛出了多个异常,子类覆盖方法时,只能抛出父类异常的子集 3. 父类没有抛出异常子类不可抛出异常 4. 子类发生非运行时异常,需要进行try{}catch的(){}处理,不能抛出。 5. 子类不能比父类抛出更多的异常

接下来我们来看异常处理的最后一部分,finally

finally

我们知道,当程序出现异常的时候,经过异常处理,程序会停止执行,所以,在处理完异常以后,后续的代码将不会执行,这样肯定是有一定问题的,比如,我们调用了资源,需要关闭这个资源的时候,程序停止了,资源却仍然被占用着,这样是不是很有问题啊。为了处理这样的问题,就要使用finally块。 finally就是必须执行的代码。 1. 出现问题的情况: try{ // 可能发生异常的代码 } catch( 异常类的类型 e ){ // 当发生指定异常的时候的处理代码 }catch… 比较适合用于专门的处理异常的代码,不适合释放资源的代码。 2. 实现方式一: try{ } catch(){} finally{ // 释放资源的代码 } finally块是程序在正常情况下或异常情况下都会运行的。 比较适合用于既要处理异常又有资源释放的代码 3. 实现方式二 try{ }finally{ // 释放资源 } 比较适合处理的都是运行时异常且有资源释放的代码。 4. finally:关键字主要用于释放系统资源。 - 在处理异常的时候该语句块只能有一个。 - 无论程序正常还是异常,都执行finally。 5. finally是否永远都执行? - 只有一种情况,但是如果JVM退出了System.exit(0),finally就不执行。 - return都不能停止finally的执行过程。 以上,就是异常处理的全部内容。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券