首页 > 编程笔记 > Java笔记 阅读:14

Java自定义异常类的创建和使用(附带实例)

在实际开发中,Java 提供的异常类可能不适合用来处理我们的现实业务,这时需要通过自定义异常来完成对实际业务的实现。

例如,在统计信息时要求年龄必须合理,这时就可以使用自定义异常来完成,当输入的年龄合理时则正常使用,当输入的年龄不合理时(如 -1、1000)则抛出自定义异常。可以通过扩展 Exception 类或 RuntimeException 类来实现自定义异常的创建。

创建自定义异常类,大体可以通过以下几个步骤来完成:
接下来,通过案例来演示自定义异常的使用:
// 自定义异常,继承Exception类
class CustomException extends Exception {
    public CustomException() {
        super();
    }

    public CustomException(String message) {
        super(message);
    }
}

public class Demo {
    public static void main(String[] args) {
        try {
            final int divide = divide(5, 0);  // 调用divide()方法
            System.out.println(divide);      // 打印结果
        } catch (CustomException e) {
            e.printStackTrace();
        }
        System.out.println("程序结束");
    }

    // 下面的方法实现了两个整数相除
    public static int divide(int x, int y) {
        if (y == 0) {
            throw new CustomException("错误:除数不能为0!");
        }
        int result = x / y;  // 定义一个变量result,记录两个数的商
        return result;       // 将结果返回
    }
}
程序的运行结果如下:

Error:(17, 23) java: 在相应的 try 语句主体中不能抛出异常错误
Error:(26, 25) java: 未报告的异常错误; 必须对其进行捕获或声明以便抛出

程序中,提示的第 1 个异常是因为系统不能确定 try 语句块中抛出的是什么类型的异常,但是如果将 catch 语句块后边的异常类型改为 Exception 时,是可以正常编译通过的,因为 Exception 类是所有异常类的基类。

提示的第 2 个异常,原因在于 divide() 方法中使用 throw 关键字进行了 CustomException 对象的抛出,而 Exception 类及其子类都是必检异常,因此必须对抛出的异常进行捕获或声明。

对前面的实例代码进行整改:
接下来,我们使用第 2 种方法将程序进行修改:
// 自定义异常,继承Exception类
class CustomException extends Exception {
    public CustomException() {
        super();
    }

    public CustomException(String message) {
        super(message);
    }
}

public class Demo {
    public static void main(String[] args) {
        try {
            final int divide = divide(5, 0);  // 调用divide()方法
            System.out.println(divide);      // 打印结果
        } catch (CustomException e) {
            System.out.println(e.getMessage());
        }
        System.out.println("程序结束");
    }

    // 下面的方法实现了两个整数相除
    public static int divide(int x, int y) throws CustomException {
        if (y == 0) {
            throw new CustomException("错误:除数不能为0!");
        }
        int result = x / y;  // 定义一个变量result,记录两个数相除的结果
        return result;       // 将结果返回
    }
}
程序的运行结果如下:

错误:除数不能为0!
程序结束

程序中,自定义异常类 CustomException 继承 Exception 类,divide() 方法使用 throw 关键字抛出 CustomException 类的实例,并使用 throws 子句声明抛出该异常。从运行结果中发现,try-catch 成功将自定义异常捕获。

Java异常丢失现象

Java 的异常处理机制可以帮助开发人员解决很多问题,但是并不代表它没有任何问题,其实 Java 的异常捕获也有瑕疵。

当在 try-finally 中出现 return 语句或是进行手动异常抛出时,就会发现即使在 try 中捕捉到了异常,但是最后输出的错误信息也不是 try 中出现的异常信息,而是 finally 中出现的错误信息,这种现象被称为异常丢失。

一旦产生异常丢失现象,那么开发人员可能就会被错误信息误导,从而进行错误的判断和处理。

接下来,通过案例来演示异常丢失现象及其对程序开发的干扰:
public class Demo {
    void method1() throws MyException1 {  // 手动抛出异常
        throw new MyException1();
    }

    void method2() throws MyException2 {  // 手动抛出异常
        throw new MyException2();
    }

    public static void main(String[] args) {
        Demo0816 demo = new Demo0816();
        try {
            demo.method1();
        } finally {
            demo.method2();
        }
    } catch (Exception e) {
        System.out.println(e);
    }
}

class MyException1 extends Exception {
    @Override
    public String toString() {
        return "异常丢失问题, MyException1类...";
    }
}

class MyException2 extends Exception {
    @Override
    public String toString() {
        return "异常丢失问题, MyException2类...";
    }
}
程序的运行结果如下:

异常丢失问题,MyException2类…

程序中,输出的结果为“异常丢失现象,MyException2类…”,main() 方法中原本被抛出的 MyException1 异常并没有被捕获,这是由于 Java 虚拟机的机制造成的一点缺陷。

产生这一问题的原因在于,finally 中的代码一般是用于关闭资源的代码(如关闭文件、网络链接等),故而当程序执行至 try-catch 语句块的“边界处”时,便会转入 finally 语句块,而 throw 语句在字节码层面并非原语操作,所以当上面的程序在执行到第 3 行时,Java 虚拟机会将要抛出的异常对象的引用存放到一个局部变量里,并将该变量存到方法栈的栈顶等待弹出,此时程序计数器指针指向 finally 内的代码,遇到下一个要抛出的异常时,该异常则顶替 MyException1 的对象引用所在位置,所以程序只会输出“异常丢失问题,MyException2类…”。

同理,在 finally 里也不要处理返回值。当返回值在 finally 语句块外返回时,由于 throw 并非原子语句,所以会用中间变量存储中间值,导致 finally 内处理的返回值并不能体现在返回的实际值上。

相关文章