在Java中扩展Throwable

时间:2010-05-03 03:29:25

标签: java exception-handling throwable

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

public class FlyingPig extends Throwable { ... }

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

throw new FlyingPig("Oink!");

当然其他地方:

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

我的问题是:

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

建议的方案#1

真的想要做类似这样的事情的场景具有以下属性:

  • “事件”是 最终会发生的事情。 预期。它绝对不是Error,并且没有Exception - 关于它何时发生。
    • 因为预期,会有catch等待它。它不会“滑倒”过去。它不会“逃避”任何catch一般Exception和/或Error的尝试。
  • “事件”发生极少
  • 当它发生时,通常会有一个深层堆栈跟踪。

所以也许我现在很清楚我要说的是:FlyingPig是详尽的递归搜索的结果

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

当找到所寻找的对象时,只需throw - FlyingPig,您就不必使用boolean isFound标志的管理来混乱代码。在这方面,代码不仅更清晰,而且由于这种遗漏,它可能会运行得更快。

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

  • 传统的控制流方法
    • 使用boolean isFound,连续检查
    • 99.99%的时间,支票是“浪费”,因为它仍然是false
    • 当它最终成为true时,您将停止递归,并且您必须确保可以正确地展开初始调用。
  • FlyingPig接近
    • 不要打扰任何boolean isFound
    • 如果找到,只需throw new FlyingPig();它是预期,因此它会有catch
    • 没有管理boolean标志,没有浪费检查是否需要继续进行,没有记账手动解除递归等等。

问题:

  • 这种(ab)技术使用异常是否有效? (有名字吗?)
  • 如果有效,FlyingPig extends ThrowableException应该没问题? (即使它的情况没有什么特别之处?)

7 个答案:

答案 0 :(得分:32)

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

我认为没有充分理由延长Throwable。改为ExceptionRuntimeException

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

异常是处理“正常”流量控制的一种非常昂贵的方式。在某些情况下,我们正在讨论数千个执行的额外指令,以创建,抛出和捕获异常。如果您要忽略已接受的智慧并使用异常进行非异常流控制,请使用Exception子类型。试图假装某事是一个“事件”,而不是“例外”,宣称它是Throwable的子类型无法实现任何目标。

然而,将异常与错误,错误,错误等混淆是错误的。并且没有错误使用Exception的子类表示“非错误,错误,错误或其他”的异常事件。关键是该事件应该例外;也就是说,与众不同,很少发生......,

总之,FlyingPig可能不是错误,但没有理由不将其声明为Exception的子类型。

答案 1 :(得分:8)

  

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

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

  • 它违反了principle of least astonishment,它使代码更难阅读,特别是如果没有任何例外情况的话。
  • 抛出异常是Java中非常昂贵的操作(虽然这可能不是问题)。

例外应该是not be used for flow control。如果我不得不说出这种技术,我会把它称为代码气味或反模式。

另见:

  

如果有效,FlyingPigThrowable延长,还是Exception是否合适? (即使它的情况没有什么特别之处?)

在某些情况下,您可能希望捕捉 Throwable,不仅要抓住Exception ,还要 Error,但这种情况很少见人们通常不会抓住Throwable。但是我找不到你想要抛出 Throwable的子类的情况。

我还认为延长Throwable并不会使事情看起来更少“exception-al”,它们会让事情变得更糟 - 这完全打败了意图。

总而言之,如果你真的想扔东西,抛出一个Exception的子类。

另见:

答案 2 :(得分:7)

以下是来自HotSpot架构师John Rose的博客文章:

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

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

我认为如果客户“隐藏”这种技术是合理的。 IE,你的FlyingPig永远不能离开你的图书馆(所有公共方法都应该保证不丢弃它)。保证这一点的一种方法是使其成为一个检查过的例外。

我认为扩展Throwable的唯一理由是因为你想允许人们传递具有catch(异常e)子句的回调,并且希望你的结果被他们忽略。我可以买那个......

答案 3 :(得分:4)

org.junit.Test注释包含扩展None的{​​{1}}类,并用作Throwable注释参数的默认值。

答案 4 :(得分:4)

如果你能证明FlyingPig与Error和Exception的区别,那么它就不适合作为它们的子类,那么创建它就没有根本的错误。

我能想到的最大问题是在务实的世界中,有时候有合理的理由来捕获java.lang.Exception。你的新类型的throwable将会飞过try-catch块,它们会尽可能地抑制(或记录,包装,等等)每个可能的非致命问题。

另一方面,如果你正在对一个 un 有理由压制java.lang.Exception的旧系统进行维护,你可能会欺骗它。 (假设真正呼吁时间真正正确地解决它被拒绝)。

答案 5 :(得分:2)

  

随着这个问题的发展,我明白了   我误解了这一点   原来的问题,所以有些   其他答案可能更多   相关。我会留下这个答案   在这里,因为它可能仍然有用   其他有类似问题的人,   并在搜索时找到此页面   回答他们的问题。

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

  • 使用最具体的异常是一个好主意。它将允许调用者以不同方式处理不同类型的异常。为了记住,异常的类层次结构非常重要,因此您的自定义异常应该扩展另一种类型的Throwable,尽可能接近您的异常。
  • 延长Throwable可能太高级了。尝试改为扩展Exception或RuntimeException(或者更接近抛出异常的原因的低级异常)。请记住RuntimeException和Exception之间的区别。
  • 对抛出异常(或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,因为FlyingPigSwine都会延伸Pig。如果你想做同样的事情来处理任何一个异常,这很有用。但是,您可以采用不同的方式处理它们:

try {
    bar();
} catch (FlyingPig e) {
    ...
} catch (Swine e) {
    ...
}

答案 6 :(得分:2)

Play! framework使用类似的东西来处理请求。请求处理遍历多个层(路由,中间件,控制器,模板呈现),在最后一层,呈现的HTML为wrapped in a throwable并抛出,最顶层捕获,解包并发送到客户端。因此,涉及的许多层中的所有方法都不需要显式返回响应对象,也不需要将响应对象作为参数传递以进行传播和修改。

我对细节有点粗略。你可以浏览Play的代码!细节框架。