3.2 自定义异常
前面讨论了如何处理调用Java API的方法时产生的异常。根据需要,还可创建和使用自定义异常——自我构建表示错误的类。可创建全新异常,并将它们用于应用程序。
使用自定义异常有什么好处呢?为何要定义新异常类型?创建自定义异常是为了表示应用程序的一些错误类型,为代码可能发生的一个或多个问题提供新含义。可以显示代码多个位置之间的错误的相似性,也可区分代码运行时可能出现的相似问题的一个或多个错误,或给出应用程序中一组错误的特定含义。
例如,考虑任何类型的服务器。服务器的基本作用是处理与客户机的通信。若使用标准Java API(如java.io和java.net包中的类)来编写服务器,则可使编写的代码在多个位置抛IOException。 在设置服务器、等待客户机连接和获取通信流时,可抛出IOExceptions;在通信期间及试图断开连接时,也可抛出IOExceptions。简言之,服务器的各个部分都可能引发IOException。
对服务器而言,这些IOException意义不尽相同。虽然由同一异常类型表示,但与各个异常相关的业务含义存在差异,报告和恢复操作亦有不同。可以将一个异常集与服务器配置和启动问题关联,将另一个异常集与客户机通信的实际行动关联,将第三个异常集与服务器关闭任务关联。使用自定义异常,可采用对应用程序有意义的方式来灵活地表示错误。
创建和使用自定义异常并不难。遵循以下3个步骤即可。
3.2.1 定义异常类
一般要定义新类来表示自定义异常。多数情况下,只需创建已有异常类的子类。
1 public class CustomerExistsException extends Exception{
2 public CustomerExistsException(){}
3 public CustomerExistsException(String message){
4 super(message);
5 }
6 }
至少要继承Throwable或Throwable的子类。经常需要定义一个或多个构造函数,以在对象中存储错误消息。如第2-4行所示。在继承任何异常时,将自动继承Throwable类的一些标准特性,如:
● 错误消息
● 栈跟踪
● 异常包装
若要在异常中添加附加信息,则可以为类添加一些变量和方法:
1 public class CustomerExistsException extends Exception{
2 private String customerName;
3 public CustomerExistsException(){}
4 public CustomerExistsException(String message){
5 super(message);
6 }
7 public CustomerExistsException(String message, String customer){
8 super(message);
9 customerName = customer;
10 }
11 public String getCustomerName(){
12 return customerName;
13 }
14 }
由本例可知,可修改CustomerExistsException类,以支持其他属性。例如,可将customerName字符串(引发异常的记录的客户名)与异常联系起来。
3.2.2 声明方法抛出自定义异常
这实际上是“处理或声明”规则的“声明”部分。为了使用自定义异常,必须通知调用代码的类:要准备处理这个异常类型。为此,声明一个或多个方法抛出异常:
public void insertCustomer(Customer c) throws CustomerExistsException{
// The method stores customer information in the database.
// If the customer data already exists, the method creates
// and throws the CustomerExistsException.
}
3.2.3 找到故障点,新建异常并加上关键字throw
最后一步实际上是创建对象,并通过系统传送该对象。为此,需要了解代码将在方法的哪个位置出现故障。根据情况,可能要使用以下部分或所有条件,来指示代码中的故障点。
1. 外部问题
● 应用程序中产生的异常
● 其他方法返回的故障代码
2. 内部问题
● 应用程序状态不一致
● 应用程序中的处理问题
在本例中,当不能新建一个客户时会遇到一个故障场景。结果,创建一个异常来表示问题并抛出该问题。如下面的示例方法所示:
1 public void insertCustomer(Customer c)
2 throws CustomerExistsException, SQLException {
3 String selectSql =
4 "SELECT * FROM Customer WHERE first_name=? AND last_name=?";
5 String insertSql = "INSERT INTO Customer VALUES(?, ?)";
6 try{
7 Connection conn = dbmsConnectionFactory.getConnection();
8 PreparedStatement selStmt = conn.prepareStatement(selectSql);
9 selectStmt.setString(1, c.getFirstName());
10 selectStmt.setString(2, c.getLastName());
11 ResultSet rs = selStmt.executeQuery();
12 if (rs.next()){
13 // In this case, the failure condition is produced if you
14 // can already locate a metching record in the database.
15 throw new CustomerExistsException("Customer exists:" + c, c);
16 }
17 else{
18 PreparedStatement insStmt = conn.prepareStatement(insertSql);
19 insStmt.setString(1, c.getFirstName());
20 insStmt.setString(2, c.getLastName());
21 int status = insStmt.executeUpdate();
22 }
23 }
24 catch (SQLException exc){
Java关键字throw将这个新异常对象传给该方法的调用者。在执行完这3个步骤后,就创建了自定义异常。除非派生一个非检测异常类(如RuntimeException或Error),否则调用方法的任何对象随后将按照“处理或声明”规则解决该异常。
这引出了一个有趣的问题:在自定义异常时,应如何派生?必须在Throwable类层次结构中派生,否则将不能在应用程序中传播异常。另外,不能从Throwable直接派生。Throwable为两类主要问题(Exception和Error)提供行为基础,不能为这棵继承树定义新分支。一般也不要直接继承Error或其任何子类,因为自定义异常通常不符合错误标准(即适当应用程序不应试图捕获的严重问题)。
需要从Exception类层次结构中派生。一般地,应将自定义异常定义为故障状态更一般的异常类型的子类。例如,ServerConnectionException是java.io.IOException的子类,因为Server- Connec tionException是java.io.IOException的更具体类型。
如果定义的异常从RuntimeException树继承,是否属于正确的编码实践?若如此,就回避了异常机制,即使声明了异常,类也不必显式处理异常。
通过本例,可了解到如何创建基本的自定义异常。很多情况下,这可轻易地满足要求。自定义异常类(可能还有消息)经常是应用程序惟一需要的异常。有时,需要支持更高级的特性,在一些异常中,可能要用到两个属性:链表(chaining)和本地化(localization)。