Java自定义异常类的创建和使用(附带实例)
在实际开发中,Java 提供的异常类可能不适合用来处理我们的现实业务,这时需要通过自定义异常来完成对实际业务的实现。
例如,在统计信息时要求年龄必须合理,这时就可以使用自定义异常来完成,当输入的年龄合理时则正常使用,当输入的年龄不合理时(如 -1、1000)则抛出自定义异常。可以通过扩展 Exception 类或 RuntimeException 类来实现自定义异常的创建。
创建自定义异常类,大体可以通过以下几个步骤来完成:
接下来,通过案例来演示自定义异常的使用:
提示的第 2 个异常,原因在于 divide() 方法中使用 throw 关键字进行了 CustomException 对象的抛出,而 Exception 类及其子类都是必检异常,因此必须对抛出的异常进行捕获或声明。
对前面的实例代码进行整改:
接下来,我们使用第 2 种方法将程序进行修改:
当在 try-finally 中出现 return 语句或是进行手动异常抛出时,就会发现即使在 try 中捕捉到了异常,但是最后输出的错误信息也不是 try 中出现的异常信息,而是 finally 中出现的错误信息,这种现象被称为异常丢失。
一旦产生异常丢失现象,那么开发人员可能就会被错误信息误导,从而进行错误的判断和处理。
接下来,通过案例来演示异常丢失现象及其对程序开发的干扰:
产生这一问题的原因在于,finally 中的代码一般是用于关闭资源的代码(如关闭文件、网络链接等),故而当程序执行至 try-catch 语句块的“边界处”时,便会转入 finally 语句块,而 throw 语句在字节码层面并非原语操作,所以当上面的程序在执行到第 3 行时,Java 虚拟机会将要抛出的异常对象的引用存放到一个局部变量里,并将该变量存到方法栈的栈顶等待弹出,此时程序计数器指针指向 finally 内的代码,遇到下一个要抛出的异常时,该异常则顶替 MyException1 的对象引用所在位置,所以程序只会输出“异常丢失问题,MyException2类…”。
同理,在 finally 里也不要处理返回值。当返回值在 finally 语句块外返回时,由于 throw 并非原子语句,所以会用中间变量存储中间值,导致 finally 内处理的返回值并不能体现在返回的实际值上。
例如,在统计信息时要求年龄必须合理,这时就可以使用自定义异常来完成,当输入的年龄合理时则正常使用,当输入的年龄不合理时(如 -1、1000)则抛出自定义异常。可以通过扩展 Exception 类或 RuntimeException 类来实现自定义异常的创建。
创建自定义异常类,大体可以通过以下几个步骤来完成:
- 1) 创建自定义异常类,但是该类需要继承 Exception 基类,如果自定义运行时异常则需要继承 RuntimeException 基类;
- 2) 定义构造方法;
- 3) 使用异常。
接下来,通过案例来演示自定义异常的使用:
// 自定义异常,继承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: 未报告的异常错误; 必须对其进行捕获或声明以便抛出
提示的第 2 个异常,原因在于 divide() 方法中使用 throw 关键字进行了 CustomException 对象的抛出,而 Exception 类及其子类都是必检异常,因此必须对抛出的异常进行捕获或声明。
对前面的实例代码进行整改:
- 第 1 种方法是在 divide() 方法中使用 try-catch 对异常进行捕获处理;
- 第 2 种方法是使用 throws 子句声明抛出 CustomException 异常。
接下来,我们使用第 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!
程序结束
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 内处理的返回值并不能体现在返回的实际值上。