31

Java 允许您创建一个全新的子类型Throwable,例如:

public class FlyingPig extends Throwable { ... }

现在,很少,我可能会做这样的事情:

throw new FlyingPig("Oink!");

当然还有其他地方:

try { ... } catch (FlyingPig porky) { ... }

我的问题是:

  • 这是一个坏主意吗?如果是这样,为什么?
    • 如果这是一个坏主意,可以做些什么来防止这种子类型化?
    • 由于它是不可预防的(据我所知),可能会导致什么灾难?
  • 如果这不是一个坏主意,为什么不呢?
    • 你怎么能做出有用的事情extends Throwable呢?

提议的场景#1

我真的很想做这样的事情的场景具有以下属性:

  • “事件”是最终发生的事情。预计。_ 它绝对不是一个,而且它什么时候发生 Error也没有-al 。Exception
    • 因为在意料之中,所以会有一个catch等待。它不会“溜走”任何东西。它不会“逃避”任何对catch一般Exception和/或Error.
  • “事件”很少发生。
  • 当它发生时,通常会有很深的堆栈跟踪。

所以也许现在我想说的很清楚:FlyingPig是穷举递归搜索的结果

要搜索的对象是存在的:只要在搜索空间的大海中找到它。搜索过程将是一个漫长的过程,因此异常处理相对昂贵的成本可以忽略不计。事实上,使用boolean isFound标志的传统控制流构造替代方案可能更昂贵,因为它必须在整个搜索过程中不断检查,很可能在递归的每个级别。此检查将在 99.99% 的情况下失败,但传播终止条件是绝对必要的。在某种程度上,虽然有效,但检查效率低下

通过在找到所寻找的对象时简单地throw-ing a FlyingPig,您不必将代码与boolean isFound标志的管理混为一谈。不仅在这方面代码更干净,而且由于这个遗漏它可能运行得更快。

总而言之,选择是在这两者之间:

  • 传统的控制流方法
    • 使用 a boolean isFound,连续检查
    • 99.99% 的时间,支票是一种“浪费”,因为它仍然是false
    • 当它最终变成 时true,你停止递归,你必须确保你可以正确地放松到最初的调用。
  • FlyingPig方法
    • 不要打扰任何boolean isFound.
    • 如果找到,就throw new FlyingPig(); 这是预期的,所以会有一个catch
    • 没有boolean标志管理,如果你需要继续前进,没有浪费的检查,没有手动展开递归的簿记等。

问题:

  • 这种(ab)使用异常的技术是否有效?(有名字吗?)
  • 如果有效,应该FlyingPig extends Throwable,还是Exception很好?(即使它的情况没有什么特别的?)
4

7 回答 7

36

我会说这是一个非常糟糕的主意。很多代码是在假设如果你捕获Error并且Exception你已经捕获了所有可能的异常的情况下实现的。大多数教程和教科书都会告诉你同样的事情。通过创建Throwable您的直接子类,可能会产生各种维护和互操作性问题。

我想不出什么好理由来延长Throwable。扩展ExceptionRuntimeException代替。

编辑 - 响应 OP 提出的方案 #1。

异常是处理“正常”流控制的一种非常昂贵的方式。在某些情况下,我们说的是为了创建、抛出和捕获异常而执行的数千条额外指令。如果您要忽略公认的智慧并使用异常进行非异常流控制,请使用Exception子类型。试图通过声明 is 作为子类型来假装某事是“事件”而不是“异常”Throwable不会实现任何目标。

但是,将异常与错误、错误、错误等混为一谈是错误的。使用Exception. _ _ 关键是事件应该是例外的;即不寻常,很少发生,...

总之, aFlyingPig可能不是错误,但这不是不将其声明为 的子类型的理由Exception

于 2010-05-03T03:41:17.690 回答
8

这种(ab)使用异常的技术是否有效?(有名字吗?)

在我看来,这不是一个好主意:

  • 它违反了最小惊讶原则,它使代码更难阅读,尤其是在没有任何异常的情况下。
  • 抛出异常在 Java 中是一项非常昂贵的操作(尽管在这里可能不是问题)。

异常不应该用于流控制。如果我必须为这种技术命名,我会称其为代码异味或反模式。

也可以看看:

如果有效,应该FlyingPigextends Throwable,还是Exception很好?(即使它的情况没有什么特别的?)

在某些情况下,您不仅想抓,而且还想 ,但这种情况很少见,人们通常不抓。但是我找不到你想抛出一个子类的情况。ThrowableException ErrorThrowableThrowable

而且我还认为扩展Throwable不会让事情看起来不那么“异常”,它们会让事情看起来更糟——这完全违背了意图。

所以总结一下,如果你真的想扔东西,扔一个Exception.

也可以看看:

于 2010-05-03T07:16:44.637 回答
7

这是 HotSpot 架构师 John Rose 的博客文章:

http://blogs.oracle.com/jrose/entry/longjumps_considered_inexpensive

它是关于流控制的“滥用”异常。用例略有不同,但是.. 简而言之,它工作得非常好 - 如果您预先分配/克隆您的异常以防止创建堆栈跟踪。

我认为如果对客户“隐藏”,这种技术是合理的。IE,你的 FlyingPig 永远不能离开你的库(所有公共方法都应该传递性地保证不会抛出它)。保证这一点的一种方法是将其设置为已检查的异常。

我认为扩展 Throwable 的唯一理由是因为您希望允许人们传入具有 catch(Exception e) 子句的回调,并且希望您的结果被他们忽略。我只能买那个了...

于 2010-08-30T12:49:01.987 回答
4

org.junit.Test注释包括None扩展Throwable并用作expected注释参数的默认值的类。

于 2010-05-03T03:40:53.097 回答
4

如果您可以证明 FlyingPig 与 Error 和 Exception 不同的原因是什么,以至于它不适合作为任何一个的子类,那么创建它从根本上没有错。

我能想到的最大问题是在务实的世界里,有时有正当的理由去捕捉 java.lang.Exception。您的新型 throwable 将飞过 try-catch 块,这些块具有抑制(或记录、包装等)所有可能的非致命问题的合理期望。

另一方面,如果您正在对不合理地抑制 java.lang.Exception 的旧系统进行维护您可以绕开它作弊。(假设真诚地呼吁时间实际正确修复它被拒绝)。

于 2010-05-03T03:48:59.173 回答
2

随着这个问题的发展,我发现我误解了原始问题的重点,因此其他一些答案可能更相关。我将把这个答案留在这里,因为它可能仍然对其他有类似问题的人有所帮助,并在搜索他们问题的答案时找到这个页面。

将 Throwable 扩展为能够抛出(和处理)自定义异常并没有错。但是,您应该牢记以下几点:

  • 尽可能使用最具体的异常是一个好主意。它将允许调用者以不同的方式处理不同类型的异常。记住异常的类层次结构很重要,因此您的自定义异常应该扩展另一种类型的 Throwable,该类型尽可能接近您的异常。
  • 扩展 Throwable 可能太高级了。尝试扩展 Exception 或 RuntimeException (或更接近您引发异常的原因的较低级别的异常)。请记住 RuntimeException 和 Exception 之间的区别。
  • 对抛出异常(或异常的子类)的方法的调用将需要包装在能够处理异常的 try/catch 块中。这适用于由于可能无法控制的情况(例如,网络中断)而导致事情出错的情况。
  • 对抛出 RuntimeException(或其子类)的方法的调用不需要包装在可以处理异常的 try/catch 块中。(当然可以,但不需要。)这更适用于确实不应该出现的异常。

因此,假设您的代码库中有以下异常类:

public class Pig extends Throwable { ... }
public class FlyingPig extends Pig { ... }
public class Swine extends Pig { ... }
public class CustomRuntimeException extends RuntimeException { ... }

还有一些方法

public void foo() throws Pig { ... }
public void bar() throws FlyingPig, Swine { ... }
// suppose this next method could throw a CustomRuntimeException, but it
// doesn't need to be declared, since CustomRuntimeException is a subclass
// of RuntimeException
public void baz() { ... } 

现在,您可以使用一些代码来调用这些方法,如下所示:

try {
    foo();
} catch (Pig e) {
    ...
}

try {
    bar();
} catch (Pig e) {
    ...
}

baz();

请注意,当我们调用时bar(),我们可以只捕获Pig,因为两者都FlyingPig可以Swine扩展Pig。如果您想做同样的事情来处理任何一个异常,这很有用。但是,您可以以不同的方式处理它们:

try {
    bar();
} catch (FlyingPig e) {
    ...
} catch (Swine e) {
    ...
}
于 2010-05-03T03:35:34.627 回答
2

戏剧!框架使用这样的东西来处理请求。请求处理经过许多层(路由、中间件、控制器、模板渲染),在最后一层,渲染的 HTML 被包裹在一个 throwable和 throwable 中,最顶层捕获、解包并发送给客户端。因此,所涉及的许多层中的方法都不需要显式返回响应对象,也不需要将响应对象作为参数传递以进行传播和修改。

我对细节有点粗略。您可以查看 Play 的代码!详细框架。

于 2010-08-30T13:26:43.893 回答