Java自定义异常(小白必读)
自定义异常是指由开发者创建的,用于代表特定类型异常情况的异常类。
虽然 Java 本身已经提供了很多异常,但这些异常在实际应用中往往并不够用。例如,当想要增加数据操作时,可能会出现错误的数据,这些错误的数据一旦出现就应该抛出异常,如 AddException。但是 Java 并不提供这样的异常,因此,需要由用户开发一个自定义异常类。
在程序中,可能会遇到 JDK 提供的任何标准异常类都无法充分描述清楚我们想要表达的问题,在这种情况下可以创建自己的异常类,即自定义异常类。
创建自定义异常,通常是创建一个 Exception 类或 RuntimeException 类的子类,并在类名中明确此自定义异常所代表的异常情况,需做到见名知意。如果自定义异常类继承 Exception 类,那么该类是受检异常。如果自定义异常类继承 RuntimeException 类,那么该类是非受检异常。开发者需要根据实际的应用场景选择具体继承哪个父类。
通常,自定义异常类应该包含两个构造器:一个是默认的构造器,另一个是带有详细信息的构造器。
在创建好自定义异常类之后,开发者还应该在类中显式声明构造器,并在构造器中使用 super() 方法调用父类对应参数的构造器,这样做和 Throwable 类的设计有关。Throwable 类中设计的 detailMessage 属性和 cause 属性均使用 private 修饰,同时并未提供相应的 set() 方法。这样的设计使 detailMessage 和 cause 成为只读属性,即其值一旦被初始化,将没有渠道进行修改。类似的属性可以通过构造器进行初始化,因此,需要保证在子类的构造器中显式调用父类对应参数的构造器,以保证这些属性得到了初始化。
如下展示了一个代表用户访问被拒绝的自定义异常。
例如,在登录功能中,用户提供了错误的用户名,当程序使用该用户名从数据库中查询用户信息时,可能会因为查不到对应的记录而出现 NullPointerException,但是开发者或运维人员无法通过这个异常直观识别是程序的哪个模块或哪个功能出现了异常。针对这种情况,在开发过程中可以先声明一个自定义异常 UserNotFoundException,再使用 try-catch 语句块捕获 NullPointerException,最后在 catch 语句块中创建 UserNotFoundException 类型的异常对象,封装 NullPointerException 异常对象的信息,向上抛出统一异常处理方法。此时,开发者或运维人员得到的是 UserNotFoundException 异常对象,即可以直观地识别程序出现了什么问题。这种处理方法也被称为捕获再抛出。
【实例】自定义异常的应用。
虽然 Java 本身已经提供了很多异常,但这些异常在实际应用中往往并不够用。例如,当想要增加数据操作时,可能会出现错误的数据,这些错误的数据一旦出现就应该抛出异常,如 AddException。但是 Java 并不提供这样的异常,因此,需要由用户开发一个自定义异常类。
在程序中,可能会遇到 JDK 提供的任何标准异常类都无法充分描述清楚我们想要表达的问题,在这种情况下可以创建自己的异常类,即自定义异常类。
创建自定义异常,通常是创建一个 Exception 类或 RuntimeException 类的子类,并在类名中明确此自定义异常所代表的异常情况,需做到见名知意。如果自定义异常类继承 Exception 类,那么该类是受检异常。如果自定义异常类继承 RuntimeException 类,那么该类是非受检异常。开发者需要根据实际的应用场景选择具体继承哪个父类。
通常,自定义异常类应该包含两个构造器:一个是默认的构造器,另一个是带有详细信息的构造器。
在创建好自定义异常类之后,开发者还应该在类中显式声明构造器,并在构造器中使用 super() 方法调用父类对应参数的构造器,这样做和 Throwable 类的设计有关。Throwable 类中设计的 detailMessage 属性和 cause 属性均使用 private 修饰,同时并未提供相应的 set() 方法。这样的设计使 detailMessage 和 cause 成为只读属性,即其值一旦被初始化,将没有渠道进行修改。类似的属性可以通过构造器进行初始化,因此,需要保证在子类的构造器中显式调用父类对应参数的构造器,以保证这些属性得到了初始化。
如下展示了一个代表用户访问被拒绝的自定义异常。
/** * 当前用户没有该记录访问权限时抛出的异常 */ public class AccessDeniedException extends Exception { public AccessDeniedException() { super(); } public AccessDeniedException(String message) { super(message); } public AccessDeniedException(String message, Throwable cause) { super(message, cause); } public AccessDeniedException(Throwable cause) { super(cause); } public AccessDeniedException(String message, Throwable cause, boolean enablesuppression, boolean writablestackTrace) { super(message, cause, enablesuppression, writablestackTrace); } }在开发过程中,自定义异常的应用非常广泛,当前最流行的 Java 高级工具和框架中都应用了自定义异常。产生这一现象的根本原因是,Java 提供的异常是通用的,是“非业务”的。
例如,在登录功能中,用户提供了错误的用户名,当程序使用该用户名从数据库中查询用户信息时,可能会因为查不到对应的记录而出现 NullPointerException,但是开发者或运维人员无法通过这个异常直观识别是程序的哪个模块或哪个功能出现了异常。针对这种情况,在开发过程中可以先声明一个自定义异常 UserNotFoundException,再使用 try-catch 语句块捕获 NullPointerException,最后在 catch 语句块中创建 UserNotFoundException 类型的异常对象,封装 NullPointerException 异常对象的信息,向上抛出统一异常处理方法。此时,开发者或运维人员得到的是 UserNotFoundException 异常对象,即可以直观地识别程序出现了什么问题。这种处理方法也被称为捕获再抛出。
【实例】自定义异常的应用。
package chapter6; public class Test { public static void main(String[] args) { Person p = new Person(); try { p.setName("Lincoln"); p.setAge(-1); } catch (IllegalAgeException e) { e.printStackTrace(); System.exit(-1); } System.out.println(p); } } package chapter6; // IllegalAgeException:非法年龄异常,继承Exception类 class IllegalAgeException extends Exception { // 默认构造器 public IllegalAgeException() { super(); } // 带有详细信息的构造器,信息存储在message中 public IllegalAgeException(String message) { super(message); } } package chapter6; public class Person { private String name; private int age; public void setName(String name) { this.name = name; } public void setAge(int age) throws IllegalAgeException { if (age < 0) { throw new IllegalAgeException("人的年龄不应该为负数"); } this.age = age; } public String toString() { return "name is " + name + " and age is " + age; } }运行结果如下图所示。