Java风格:正确处理异常
我一直在概念上为我的项目决定一个异常处理结构 例如,假设您有:Java风格:正确处理异常,java,exception,Java,Exception,我一直在概念上为我的项目决定一个异常处理结构 例如,假设您有: public abstract class Data { public abstract String read(); } 还有两个子类FileData,它从指定的文件读取数据,还有StaticData,它只返回一些预定义的常量数据 现在,在读取文件时,可能会在FileData中引发IOException,但StaticData永远不会引发IOException。大多数样式指南建议将异常向上传播到调用堆栈,直到有足够的上下文
public abstract class Data {
public abstract String read();
}
还有两个子类FileData,它从指定的文件读取数据,还有StaticData,它只返回一些预定义的常量数据
现在,在读取文件时,可能会在FileData中引发IOException,但StaticData永远不会引发IOException。大多数样式指南建议将异常向上传播到调用堆栈,直到有足够的上下文可用以有效处理它
但我并不想在abstract read()方法中添加throws子句。为什么?因为数据和使用它的复杂机器对文件一无所知,它只知道数据。此外,可能还有其他数据子类(以及更多)从不抛出异常并完美地交付数据
另一方面,IOException是必要的,因为如果磁盘不可读(或类似),则必须抛出错误。因此,我看到的唯一出路是捕获IOException并在其位置抛出一些RuntimeException
这是正确的原理吗?抛出
IOException
,该异常封装在一个适合于“数据
”类的异常类型中。事实上,read
方法并不总是能够提供数据,它可能应该指出原因。包装异常可能会扩展RuntimeException
,因此不需要声明(尽管它应该被适当地记录在案)。如果您没有明确声明read()
可以引发异常,那么当它引发异常时,您会让开发人员大吃一惊
在您的特定情况下,我将捕获底层异常,并将它们作为新的异常类重新显示DataException
或DataReadException
您应该在abstract read()方法上声明某种异常。抽象类实际上是在声明一个接口——您已经从两个具体的子类中了解到,由于异常情况,实现很可能无法成功返回
因此,在abstract Data.read()方法中声明一些要抛出的异常是完全正确的。不要试图简单地声明它抛出IOException,因为它不应该绑定到特定实现的和(否则,您必须声明,如果您决定使用数据库读取子类,它也可以抛出SQLException,如果您有基于XML的读取器(使用SAX),则抛出SAXException,等等)。您需要自己的自定义异常,以在抽象级别充分捕获此异常—类似于上面推荐的DataException,或者如果这有意义,则可能重用同一包中现有的自定义异常。您是对的
异常应该在使用的抽象级别上。这就是java 1.4 Throwable支持异常链接的原因。例如,对于使用数据库的服务,或者对于“存储”不可知的服务,没有必要抛出FileNotFoundException
可能是这样的:
public abstract class Data {
public abstract String read() throws DataUnavailableException;
}
class DataFile extends Data {
public String read() throws DataUnavailableException {
if( !this.file.exits() ) {
throw new DataUnavailableException( "Cannot read from ", file );
}
try {
....
} catch( IOException ioe ) {
throw new DataUnavailableException( ioe );
} finally {
...
}
}
class DataMemory extends Data {
public String read() {
// Everything is performed in memory. No exception expected.
}
}
class DataWebService extends Data {
public string read() throws DataUnavailableException {
// connect to some internet service
try {
...
} catch( UnknownHostException uhe ) {
throw new DataUnavailableException( uhe );
}
}
}
请记住,如果您在编程时考虑了继承,那么您应该针对特定场景进行仔细设计,并使用这些场景测试实现。显然,如果编写通用库比较困难,因为您不知道如何使用它。但大多数情况下,应用程序都局限于特定的域
您的新异常应该是运行时的还是检查的?这取决于具体情况,一般规则是为编程错误抛出运行时,并检查可恢复条件
如果可以通过正确编程避免异常(如NullPointerException或IndexOutOfBounds),请使用Runtime
如果异常是由于程序员无法控制某些外部资源造成的(例如,网络已关闭),并且可以执行某些操作(显示5分钟后重试的消息或其他操作),则应使用选中的异常
如果异常超出了程序员的控制,但什么也做不了,那么可以使用RuntimeException。例如,您应该写入一个文件,但该文件已被删除,您无法重新创建或重试,那么程序很可能会在运行时失败(对此您无能为力)
请参阅有效Java中的以下两项:
- 对可恢复条件使用检查异常,对编程错误使用运行时异常
- 抛出适合于抽象的异常
我希望这会有所帮助。使用运行时异常,并在顶部使用爆炸式的“一网打尽”。一开始有点吓人,但一旦你习惯了就会好起来 在我的web应用程序中,抛出的所有内容都是运行时。几乎没有“throws”子句,在我真正能够(或想要)处理异常的地方只有catchblock。在最高级别,有一个catch-Throwable,它呈现一个technicall错误页面,并写入一个日志条目 Log4J邮件发送给我日志条目和它前面的10个日志条目。所以当客户打电话时,我通常已经知道有问题了
通过正确的(单元)测试和干净的编程,增加的清洁度和可读性超过了任何时候检查异常的损失。我会让DataException也将IOException保存在其中,以便处理异常的级别可以在需要时向下钻取。Paul,您不需要显式地执行此操作-从Java 1.4开始,您可以使用initCause(Throwable)方法或通过实现包含Throwable作为参数的构造函数来链接异常。Java是否允许子类中的重写方法删除throws声明?如果是这样的话,如果您正在使用从不抛出的子类,则可以使用它们,而不必处理异常