异常处理

1、概述

我们在程序的设计和运行调试的过程中,不可避免会发生错误。尽管Java提供了便于写出安全代码的方法,我们开发人员也尽量的减少错误的发生,但还是避免不了。因此Java提供了异常处理机制来帮助我们检查可能出现的错误,来保证程序的可读性和可维护性。Java中是将异常封装到一个类中的,出现错误时,就会抛出这个异常类的一个异常对象。

在程序中,错误可能产生于各种原因,比如用户的坏数据、打开一个不存在的文件等。Java中将这种在程序运行时可能出现的错误称为异常,异常是在程序执行期间发生的事件,它中断了正在执行的程序的正常指令流。

以下以一个例子说明:

public class Test {

    public static void main(String[] args) {
        int result = 25 / 0;
        System.out.println(result);
    }
    
}

执行,控制台输出:
在这里插入图片描述
可以看到程序发生了ArithmeticException算数异常,后面也给出了提示/ by zero,因为0作除数,所以程序抛出了异常,程序并未执行完毕而提前结束了。

其实有很多种异常,比如常见的空指针异常、数组溢出异常等。Java是面向对象的语言,因此异常在Java中是作为类的实例出现的。当某个方法发生错误时,这个方法会创建一个异常对象,并且将它传递给正在运行的系统。通过异常的处理机制,可将非正常下的处理代码和主逻辑分离,在编写代码主要业务的同时在其他的地方处理异常,这就非常方便了。

2、异常体系结构

通过图示说明:
在这里插入图片描述
Exception的子类并不是只有上面的两种RuntimeException和IOException,它有很多的子类,不过可以大体上分为两种:运行时异常RuntimeException和非运行时异常。

Throwable类是所有异常类的顶级父类,该类有2个直接子类:Error类和Exception类。

2.1、Error类

Error类和它的子类用来描述Java运行系统中的内部错误,这种一般发生在JVM内部,属于严重错误,是无法通过程序调试来规避的,也就是说超出了我们调控的范围。比如OutOfMemoryError、ThreadDeath等,发生这种异常时,JVM会中止线程。

2.2、Exception类

Exception类称为非致命性类,这种异常程序本身是可以处理的。Exception类大体上可以分为:运行时异常和非运行时异常。这种异常我们应该尽可能的去处理,来保证代码的可读性和健壮性。

运行时异常:

运行时异常指RuntimeException类及其子类异常,如NullPointerException、IndexOutOfBoundsException等异常。这些异常属于非检查性异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。

以下列出常见的非检查性异常:
在这里插入图片描述
在这里插入图片描述
非运行时异常:

非运行时异常是指除了RuntimeException以外的异常,属于Exception类的子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。这些异常属于检查性异常,因为不通过编译是运行不了的。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。

常见的检查性异常如下:
在这里插入图片描述
我们研究的是Exception这类可处理的非致命性异常,因为研究Error异常毫无意义,它无法避免,以下都是围绕Exception类及其子类。

3、异常的声明及抛出

异常的声明和抛出一般是在方法中进行,语法特点如下:

方法名称(...) throws 异常1,异常2...{
     throw 异常对象;
}

通过throws、throw关键字来声明和抛出异常。以下代码说明。

定义一个类,类中有一个抛出异常的方法:

public class Demo {

    // 定义一个方法,并且抛出异常
    public void d1(int a) throws Exception {
        if (a < 0) {
            throw new Exception("不接受负数!");
        }
        System.out.println("2021到了!");
    }

}

测试:

public class Test {

    public static void main(String[] args) throws Exception {
        Demo d = new Demo();
        d.d1(-5);
        System.out.println("程序可以执行到这里!");
    }

}

执行,控制台:
在这里插入图片描述
可以看到,程序执行到d1方法时,由于传入了一个负数参数,所以抛出了一个异常对象,异常对象被系统捕捉到,强制终止了程序,后面的一句代码也就无法执行了。需要说明的是,当调用会抛出异常的方法时,就像上面的d1方法,要么继续往上抛,上面就是继续往上抛,要么对异常进行捕获处理,下面介绍捕获处理。

4、异常的捕获和处理

对异常的捕获处理使用try catch块,语法如下:

try{
   抛出异常的方法;
}catch(异常类型 变量名){
   异常类型的处理;
}

对上面的测试代码修改一下:

    public static void main(String[] args) {
        Demo d = new Demo();
        try {
            d.d1(5);
            System.out.println("程序没发生异常!");
        } catch (Exception e) {
            System.err.println("程序发生了异常!");
            System.err.println(e.getMessage());
            e.printStackTrace();
        }
        System.out.println("程序可以执行到这里!");
    }

执行,控制台:
在这里插入图片描述
d1方法传入了一个非负数,不会抛出异常对象,那么走try块,不会走catch块,try catch块之后的语句还是会执行。

传入一个负数,再执行:

    public static void main(String[] args) {
        Demo d = new Demo();
        try {
            // d.d1(5);
            d.d1(-5);
            System.out.println("程序没发生异常!");
        } catch (Exception e) {
            System.err.println("程序发生了异常!");
            System.err.println(e.getMessage());
            e.printStackTrace();
        }
        System.out.println("程序可以执行到这里!");
    }

控制台:
在这里插入图片描述
可以看到d1方法传入负数时,会抛出异常对象,捕获到了异常对象,那么走catch块,不会走try块,try catch块之后的内容还是会执行。

一般对于要强制执行的语句,比如上面try catch块之后的语句,不管发不发生异常,都希望它执行,那么使用finally块,一般这个finally块是和try catch块连用的,语法如下:

try{
   抛出异常的方法;
}catch(异常类型 变量名){
   异常类型的处理;
}finally{
  要强制执行的语句;
}

那么上面的测试代码再做修改:

    public static void main(String[] args) {
        Demo d = new Demo();
        try {
            // d.d1(5);
            d.d1(-5);
            System.out.println("程序没发生异常!");
        } catch (Exception e) {
            System.err.println("程序发生了异常!");
            System.err.println(e.getMessage());
            e.printStackTrace();
        } finally {
            System.out.println("程序执行到了这里!");
        }
    }

在这里插入图片描述

    public static void main(String[] args) {
        Demo d = new Demo();
        try {
             d.d1(5);
//            d.d1(-5);
            System.out.println("程序没发生异常!");
        } catch (Exception e) {
            System.err.println("程序发生了异常!");
            System.err.println(e.getMessage());
            e.printStackTrace();
        } finally {
            System.out.println("程序执行到了这里!");
        }
    }

在这里插入图片描述
如果要调用多个方法,每个方法都会抛出异常,那么按照上面的处理,是分开使用try catch块处理,如下:

try{
   抛出异常的方法1;
}catch(异常类型1 变量名){
   异常类型的处理;
}finally{
  要强制执行的语句;
}
......
try{
   抛出异常的方法2;
}catch(异常类型2 变量名){
   异常类型的处理;
}finally{
  要强制执行的语句;
}
......
try{
   抛出异常的方法3;
}catch(异常类型3 变量名){
   异常类型的处理;
}finally{
  要强制执行的语句;
}
......

每一个有异常的方法,都用一个try catch finally块来捕获处理,这样很麻烦,代码也很冗余,可以集中进行捕获和处理,如下:

try{
   抛出异常的方法1;
   抛出异常的方法2;
   抛出异常的方法3;
}catch(异常类型1 变量名){
   异常类型的处理;
}catch(异常类型2 变量名){
   异常类型的处理;
}catch(异常类型3 变量名){
   异常类型的处理;
}finally{
  要强制执行的语句1;
  要强制执行的语句2;
  要强制执行的语句3;
}

这样使用一个try块多个catch块来处理就简洁了很多,要说明的是,如果捕获的多个异常类型之间存在继承关系的话,那么子异常类需要放到父异常类的前面进行捕获。以下用代码说明:

public class Demo {

    public void d1(int a) throws Exception {
        if (a < 0) {
            throw new Exception("不接受负数!");
        }
        System.out.println("2021到了!");
    }

    public void d2(Person p) throws NullPointerException {
        if (p == null) {
            throw new NullPointerException("对象不能为空!");
        }
        System.out.println("参数合法!");
    }

}

测试:

    public static void main(String[] args) {
        Demo d = new Demo();
        Person p = null;
        try {
            d.d2(p);
            d.d1(-5);
            System.out.println("程序没发生异常!");
        } catch (NullPointerException e1) {
            e1.printStackTrace();
        } catch (Exception e2) {
            e2.printStackTrace();
        } finally {
            System.out.println("程序执行到了这里!");
        }
    }

在这里插入图片描述
尽量将抛出子异常的方法放到前面,这样就会先捕获到子类异常,因为不可能同时进入到多个catch块的。

5、常用方法

异常类提供的常用方法如下:
在这里插入图片描述
其中用的比较多的是getMessage()和printStackTrace()。

6、自定义异常类型

基本上是以Java内置的异常类可以描述编程时的大部分异常情况,一般在Java的内置异常类不能满足要求时才会创建自定义异常类,因此非必要的情况下不建议自定义异常类,自定义异常类必须要直接或间接继承Exception类。

自定义异常类的步骤大概如下:

  • 自定义类型,继承Exception类或它的子类。
  • 在方法中使用throw关键字抛出自定义的异常。
  • 调用该方法时,使用try catch块进行捕获处理或者向上抛出。

以下用一个例子说明。

自定义异常类:

/*
 * 自定义异常类,直接继承Exception类
 */
public class MyException extends Exception {

    private static final long serialVersionUID = 1L;

    // 定义2个常量
    public static final int MAX_NUM = 1000;
    public static final String MESSAGE = "集合中元素超量!";

    // 构造方法
    public MyException() {

    }

    // 覆盖父类的getMessage方法
    @Override
    public String getMessage() {
        String res = MESSAGE + "最大元素个数是:" + MAX_NUM;
        return res;
    }

    // 覆盖父类的printStackTrace方法
    @Override
    public void printStackTrace() {
        System.err.println("自定义异常类中的printStackTrace方法!");
        super.printStackTrace();
    }

}

定义异常抛出类:

/*
 * 此类中定义有异常的方法,及抛出自定义异常
 */
public class UseMyException {

    // 定义方法,抛出异常
    public List<Integer> listAdd(int size) throws MyException {
        List<Integer> list = new ArrayList<>();
        for (int i = 1; i <= size; i++) {
            // 抛出自定义异常
            if (i > MyException.MAX_NUM) {
                throw new MyException();
            }
            list.add(i);
        }
        return list;
    }

}

测试:

    public static void main(String[] args) {
        UseMyException ume = new UseMyException();
        try {
            List<Integer> list = ume.listAdd(1000);
            System.out.println("集合中的元素个数是:" + list.size());
        } catch (MyException e) {
            e.printStackTrace();
        }
    }

执行,控制台:
在这里插入图片描述
将参数改为1001:

List<Integer> list = ume.listAdd(1000);

再执行:
在这里插入图片描述

7、总结

掌握异常类型的抛出、捕获和处理,并且了解异常类的层级结构,能够自定义异常类型,开发中可能遇到各种各样的异常,熟练掌握异常的处理机制很有必要。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值