我的直觉是,在调用者可能可以以某种有益的方式恢复的情况下,应该使用已检异常,而未检异常则更适用于不可恢复的情况,但我很想听听其他人的想法。
检查异常只有在您理解何时应该使用它们时才会非常好。Java核心API未遵循这些规则,导致SQLException(有时也包括IOException)非常糟糕。
可检查异常应用于可预测的但无法防止的错误,这些错误合理地可以恢复。
不受检查的异常适用于其他所有情况。
我将为您解释一下,因为大多数人误解了这个意思。
除非您要抛出的异常满足上述所有条件,否则应使用不受检查的异常。
在每个级别重新评估:有时捕获检查异常的方法不是处理错误的正确位置。在这种情况下,请考虑对您自己的调用者来说合理的内容。如果异常是可预测的、无法防止的,并且他们可以合理地从中恢复,那么您应该自己抛出可检查的异常。否则,您应该将异常包装在不受检查的异常中。如果遵循此规则,您将发现自己根据所处的层将可检查的异常转换为不受检查的异常,反之亦然。
不论是检查异常还是未检查异常,都应该使用正确的抽象级别。例如,具有两个不同实现(数据库和文件系统)的代码仓库应该避免通过抛出SQLException
或IOException
来暴露实现特定细节。相反,它应该将异常包装在跨越所有实现的抽象中(例如RepositoryException
)。
Point::getX()
方法,那么在数据库中查找它可能是意外的;同样意外的是,该点可能没有 X 值。首先定义接口,然后考虑 - 实现独立 - 可能出错的情况。 - derM来自一位Java初学者的话:
当发生异常时,你必须要么捕获和处理异常,要么告诉编译器你无法处理它,通过声明你的方法会抛出该异常,然后使用你方法的代码将不得不处理该异常(即使它也可以选择声明会抛出异常,如果它无法处理它)。
编译器将检查我们是否做了以上两种操作之一(捕获或声明)。 所以这些被称为Checked exceptions。但Errors和Runtime Exceptions不会被编译器检测到(即使您可以选择捕获或声明,也不是必需的)。 因此,这两者被称为Unchecked exceptions。
Errors用于表示发生在应用程序外部的条件,例如系统崩溃。 Runtime exceptions通常由应用程序逻辑错误引起。 在这些情况下你什么也无法做。 当运行时异常发生时,你必须重新编写程序代码。因此,编译器不会检查这些exception。这些runtime exceptions将在开发和测试期间暴露出来。 然后我们必须重构我们的代码以消除这些错误。
foo
被记录为在读取文件结尾时抛出 barException
,但如果 foo
调用了一个抛出 barException
的方法,即使 foo
不希望这样做,调用 foo
的代码也会认为已经到达了文件结尾,并且不知道发生了意外情况。我认为这种情况应该是检查异常最有用的情况之一,但这也是编译器允许未处理检查异常的唯一情况。 - supercat你是正确的。
未检查的异常用于让系统快速失败,这是一件好事。你应该清楚地说明方法需要什么才能正常工作。这样你就只需验证输入一次。
例如:
/**
* @params operation - The operation to execute.
* @throws IllegalArgumentException if the operation is "exit"
*/
public final void execute( String operation ) {
if( "exit".equals(operation)){
throw new IllegalArgumentException("I told you not to...");
}
this.operation = operation;
.....
}
private void secretCode(){
// we perform the operation.
// at this point the opreation was validated already.
// so we don't worry that operation is "exit"
.....
}
举个例子,如果系统快速失败,那么您将知道它在哪里以及为什么失败。您将获得类似于以下的堆栈跟踪:
IllegalArgumentException: I told you not to use "exit"
at some.package.AClass.execute(Aclass.java:5)
at otherPackage.Otherlass.delegateTheWork(OtherClass.java:4569)
ar ......
你会知道发生了什么。在“delegateTheWork”方法中(位于第4569行),OtherClass使用“exit”值调用了你的类,即使它不应该等等。
否则,你将不得不在代码中到处添加验证,这样容易出错。此外,有时很难追踪出错原因,你可能需要花费数小时进行令人沮丧的调试。
NullPointerExceptions也会发生同样的情况。如果你有一个700行的类,其中包含15个方法,使用30个属性,其中没有一个属性可以为null,那么你可以将所有这些属性设置为只读,并在构造函数或工厂方法中进行验证,而不是在每个方法中验证其可空性。
public static MyClass createInstane( Object data1, Object data2 /* etc */ ){
if( data1 == null ){ throw NullPointerException( "data1 cannot be null"); }
}
// the rest of the methods don't validate data1 anymore.
public void method1(){ // don't worry, nothing is null
....
}
public void method2(){ // don't worry, nothing is null
....
}
public void method3(){ // don't worry, nothing is null
....
}
已检查异常在程序员(你或你的同事)做了一切正确的事情,验证了输入,运行了测试,所有代码都完美无缺,但代码连接到可能关闭的第三方Web服务(或者你正在使用的文件被另一个外部进程删除等)时非常有用。甚至在尝试连接之前,Web服务可能已经过验证,但在数据传输期间出现了问题。
在这种情况下,你或你的同事无法帮助它。但是你仍然必须做些什么,不能让应用程序只是死掉并消失在用户的眼中。你可以使用已检查异常来处理异常,当发生异常时,你能做什么?大多数情况下,只需要尝试记录错误,可能保存你的工作(应用程序工作),并向用户呈现一条消息(网站blabla已关闭,请稍后重试等)。
如果过度使用已检查异常(通过在所有方法签名中添加“throw Exception”),那么你的代码将变得非常脆弱,因为每个人都会忽略该异常(因为太普遍了),代码质量将严重受损。
如果过度使用未检查异常,类似的情况也会发生。那段代码的用户不知道是否会出现问题,并且会出现很多try{...}catch(Throwable t)。
这是我的"经验法则"。
与先前的答案相比,这是使用一种或两种异常的清晰理由(可以同意或不同意)。
/**
* Build a folder. <br />
* Folder located under a Parent Folder (either RootFolder or an existing Folder)
* @param aFolderName name of folder
* @param aPVob project vob containing folder (MUST NOT BE NULL)
* @param aParent parent folder containing folder
* (MUST NOT BE NULL, MUST BE IN THE SAME PVOB than aPvob)
* @param aComment comment for folder (MUST NOT BE NULL)
* @return a new folder or an existing one
* @throws CCException if any problems occurs during folder creation
* @throws AssertionFailedException if aParent is not in the same PVob
* @throws NullPointerException if aPVob or aParent or aComment is null
*/
static public Folder makeOrGetFolder(final String aFoldername, final Folder aParent,
final IPVob aPVob, final Comment aComment) throws CCException {
Folder aFolderRes = null;
if (aPVob.equals(aParent.getPVob() == false) {
// UNCHECKED EXCEPTION because the caller failed to live up
// to the documented entry criteria for this function
Assert.isLegal(false, "parent Folder must be in the same PVob than " + aPVob); }
final String ctcmd = "mkfolder " + aComment.getCommentOption() +
" -in " + getPNameFromRepoObject(aParent) + " " + aPVob.getFullName(aFolderName);
final Status st = getCleartool().executeCmd(ctcmd);
if (st.status || StringUtils.strictContains(st.message,"already exists.")) {
aFolderRes = Folder.getFolder(aFolderName, aPVob);
}
else {
// CHECKED EXCEPTION because the callee failed to respect his contract
throw new CCException.Error("Unable to make/get folder '" + aFolderName + "'");
}
return aFolderRes;
}
问题不仅仅是能否从异常中恢复。在我看来,更重要的是调用者是否有兴趣捕获异常。
如果你编写一个将在别处使用的库或应用程序的较低层级,请问一下自己:调用者是否有兴趣捕获(知道)你的异常?如果他没有兴趣,则使用未经检查的异常,这样您就不会给他造成不必要的负担。
许多框架都采用了这种哲学。特别是Spring和Hibernate - 它们将已知的已检查异常转换为未经检查的异常,因为Java中过度使用了已检查异常。我能想到的一个例子是来自json.org的JSONException,它是一个已检查异常,但大多数情况下只是麻烦 - 它应该是未经检查的,但开发人员简单地没有认真考虑过。
顺便说一句,大多数情况下,调用者对异常的兴趣与从异常中恢复的能力直接相关,但并非总是如此。
这是一个非常简单的解决方案,可以解决您Checked/Unchecked难题。
规则1:将未检查的异常视为代码执行前可测试的条件。例如...
x.doSomething(); // the code throws a NullPointerException
如果 x 为 null,代码应该可能具有以下内容:
if (x==null)
{
//do something below to make sure when x.doSomething() is executed, it won’t throw a NullPointerException.
x = new X();
}
x.doSomething();
Socket s = new Socket(“google.com”, 80);
InputStream in = s.getInputStream();
OutputStream out = s.getOutputStream();
可检查异常(Checked Exception): 如果客户端可以从异常中恢复并且希望继续执行,使用可检查异常。
不可检查异常(Unchecked Exception): 如果客户端在异常后无法继续执行任何操作,则引发不可检查异常。
例如:如果您需要在方法A()中执行算术操作,并且根据A()的输出结果,您必须执行另一种操作。如果在运行时从方法A()中收到了您不希望看到的null值,则应抛出Null pointer Exception,它是运行时异常。
请参阅此处