用Java扩展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是一个详尽的递归搜索的结果

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

通过在找到所寻找的对象时简单地throw一个FlyingPig ,您不必通过管理boolean isFound标志来混乱代码。 在这方面,代码不仅更清洁,而且由于这种遗漏,它可能运行得更快。

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

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

问题:

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

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

我认为没有充分理由延长Throwable 。 而是扩展ExceptionRuntimeException

编辑 – 响应OP提议的场景#1。

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

但是,将exception与错误,错误,错误等混淆是错误的。 使用Exception的子类表示“非错误,错误,错误或其他”的exception事件并没有错。 关键是该活动应该是特殊的 ; 即不寻常的,很少发生,……

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

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

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

  • 它违反了最不惊讶的原则 ,它使代码更难以阅读,特别是如果没有任何exception的话。
  • 抛出exception是Java中非常昂贵的操作(尽管这可能不是问题)。

不应将exception用于流量控制 。 如果我不得不说出这种技术,我会把它称为代码气味或反模式。

也可以看看:

  • 抛出exception来控制流程 – 代码味道?

如果有效, FlyingPig应该扩展Throwable ,还是Exception就好了? (即使它的情况没有什么特别之处?)

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

而且我也认为延伸Throwable不会让事情看起来不那么“exception” ,它们会使它看起来更糟 – 这完全打败了意图。

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

也可以看看:

  • 什么时候应该使用Throwable而不是新的Exception?

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

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

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

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

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

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

如果你可以certificate什么使FlyingPig与Error和Exception分开,那么它不适合作为任何一个的子类,那么创建它就没有根本的错误。

我能想到的最大问题是在实用的世界中,有时候有合理的理由来捕获java.lang.Exception。 你的新类型的throwable将会飞过try-catch块,这些块具有压缩(或记录,包装,等等)每个可能的非致命问题的所有合理期望。

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

随着这个问题的发展,我发现我误解了原问题的重点,所以其他一些答案可能更具相关性。 我会在这里留下这个答案,因为它可能对有类似问题的其他人有帮助,并在寻找他们问题的答案时找到这个页面。

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

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

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

 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 。 如果你想做同样的事情来处理任何一个exception,这很有用。 但是,您可以以不同方式处理它们:

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

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

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