重构:Java特别的接口修改:在throws子句中添加一个异常?

lhao16
  • 51

《重构:改善既有代码的设计》
P65:Java之中还有一个特别关于「修改接口」的问题:在Throws子句中增加一个异常。这并不是对签名式(signature)的修改,所以你无法以delegation(委托手法)隐 藏它。但如果用户代码不做出相应修改,编译器不会让它通过。这个问题很难解决。你可以为这个函数选择一个新名字,让旧函数调用它,并将这个新增的checked exception(可控式异常〗转换成一个unchecked exception(不可控异常:)。你也可 以拋出一个unchecked异常,不过这样你就会失去检验能力。如果你那么做,你可以警告调用者:这个unchecked异常日后会变成一个checked异常。这样他们就有时间在自己的代码中加上对此异常的处理。出于这个原因,我总是喜欢为整个package定义一个superclass异常(就像java.sql的SQLException),并确保所有public函数只在自己的throws子句中声明这个异常。这样我就可以随心所欲地定义异常,不会影响调用者,因为调用者永远只知道那个更具一般性的superclass异常。
请问这段话是什么意思?

回复
阅读 2.1k
1 个回答

我用具体的代码来解释下吧
接口A,有方法X

public interface A {
    public void methodX() throws IOException;
}

在用户模块有这样一段调用

public class ModuleUser{
    public void methodY(A a) throws IOException
    {
        a.methodX();
    }
}

现在要对方法X增加一个异常,变为:

public interface A {
    public void methodX() throws IOException,DataFormatException;
}

这样用户模块ModuleUser的方法Y就要修改,不修改,编译就不会通过。

怎么样解决呢?
方法1,新方法调用旧方法。(接口中不能新方调用旧方法,所以我改为抽象类)

public abstract class A {
    public void methodX() throws IOException
    {
        try {
            methodNewX();
        } catch (DataFormatException e) {
            throw new RuntimeException(e);
        }
    }
    
    public abstract void methodNewX() throws IOException ,DataFormatException;
}

方法2: 以拋出一个unchecked异常,unchecked就是编译器不检查的运行时异常。
如下

public interface A {
    public void methodX() throws IOException,RuntimeException;
}

这样用户代码也不用改。

但这两个方法都不好。

作者喜欢的做法是这样的(也是推荐的方式)。
在包中定义自己的异常

public class MyException extends Exception {

}

接口一开始就写成

public interface A {
    public void methodX() throws MyException;
}

用户模块就会是这样的:

public class ModuleUser{
    public void methodY(A a) throws MyException
    {
        a.methodX();
    }
}

如果再遇到上面要加异常信息的情况
就为MyException创建一个子类,加接口A上,这样用户代码就不用改,也能适配。

上面一段话大概就是想表达这个观点。
因为这本书是国外人写的,翻译成中文,所以读起来有点别扭。
英文原文是这样的

There is one particular area with problems in changing interfaces in Java: adding an exception to the throws clause.

读这本书,觉得不好理解的,就可试着看看英文原文。

宣传栏