在 Java 中扩展 Throwable

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2755972/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 22:41:17  来源:igfitidea点击:

Extending Throwable in Java

javaexception-handlingthrowable

提问by polygenelubricants

Java lets you create an entirely new subtype of Throwable, e.g:

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

public class FlyingPig extends Throwable { ... }

Now, very rarely, I may do something like this:

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

throw new FlyingPig("Oink!");

and of course elsewhere:

当然在其他地方:

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

My questions are:

我的问题是:

  • Is this a bad idea? And if so, why?
    • What could've been done to prevent this subtyping if it is a bad idea?
    • Since it's not preventable (as far as I know), what catastrophies could result?
  • If this isn't such a bad idea, why not?
    • How can you make something useful out of the fact that you can extends Throwable?
  • 这是一个坏主意吗?如果是这样,为什么?
    • 如果这是一个坏主意,可以采取什么措施来防止这种子类型化?
    • 由于它无法预防(据我所知),会导致什么灾难?
  • 如果这不是一个坏主意,为什么不呢?
    • 你怎么能从你可以的事实中做出有用的东西extends Throwable


Proposed scenario #1

提议的场景#1

A scenario where I was reallytempted to do something like this has the following properties:

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

  • The "event" is something that willhappen eventually. It is expected. It most definitely is not an Error, and there's nothing Exception-al about when it occurs.
    • Because it is expected, there will be a catchwaiting for it. It will not "slip" past anything. It will not "escape" from any attempt to catchgeneral Exceptionand/or Error.
  • The "event" happens extremely rarely.
  • When it happens, usually there's a deep stack trace.
  • “事件”是最终发生的事情。这是预期的。它绝对不是一个Error,并且没有Exception关于它何时发生的 -al 。
    • 因为是期待,所以才会有catch等待。它不会“滑过”任何东西。它不会“逃避”任何对catch一般Exception和/或Error.
  • “事件”极少发生。
  • 当它发生时,通常会有一个很深的堆栈跟踪。

So perhaps it's clear now what I'm trying to say: FlyingPigis the resultof an exhaustive recursive search.

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

The object to be searched exists: it's only a matter of finding it in the big sea that is the search space. The search process will be a long one, so the relatively expensive cost of exception handling is negligible. In fact, the traditional control flow construct alternative of using a boolean isFoundflag may be more expensive, because it has to be checked continuously throughout the search process, most likely at every level of the recursion. This check will fail 99.99% of the time, but it's absolutely necessary to propagate the termination condition. In a way, while effective, the check is inefficient!

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

By simply throw-ing a FlyingPigwhen the sought object is found, you don't have to clutter the code with the management of the boolean isFoundflag. Not only is the code cleaner in that regard, but it may run faster due to this omission.

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

So to summarize, the choice is between these two:

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

  • Traditional control-flow approach
    • Use a boolean isFound, checked continuously
    • 99.99% of the time, the check is a "waste", because it'd still be false
    • When it eventually becomes true, you stop recursing and you have to make sure that you can properly unwind to the initial call.
  • FlyingPigapproach
    • Don't bother with any boolean isFound.
    • If found, just throw new FlyingPig(); it's expected, so there will be a catchfor it.
    • No management of booleanflag, no wasted check if you need to keep going, no bookkeeping to manually unwind the recursion, etc.
  • 传统的控制流方法
    • 使用 a boolean isFound,不断检查
    • 99.99% 的情况下,支票是“浪费”,因为它仍然是 false
    • 当它最终变成 时true,您将停止递归,并且必须确保可以正确地展开到初始调用。
  • FlyingPig方法
    • 不要打扰任何boolean isFound
    • 如果找到,只需throw new FlyingPig(); 这是意料之中的,所以会有一个catch
    • 没有boolean标志的管理,如果您需要继续前进,没有浪费的检查,没有手动展开递归的簿记等。

Questions:

问题:

  • Is this technique of (ab)using exception valid? (Is there a name for it?)
  • If valid, should FlyingPig extends Throwable, or is Exceptionjust fine? (even though there's nothing exceptional about its circumstances?)
  • 这种(ab)使用异常的技术有效吗?(有名字吗?)
  • 如果有效,应该FlyingPig extends Throwable还是Exception刚刚好?(即使它的情况没有什么特别之处?)

回答by Stephen C

I'd say that it is a really bad idea. A lot of code is implemented on the assumption that if you catch Errorand Exceptionyou have caught all possible exceptions. And most tutorials and textbooks will tell you the same thing. By creating a direct subclass of Throwableyou are potentially creating all sorts of maintenance and interoperability problems.

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

I can think of no good reason to extend Throwable. Extend Exceptionor RuntimeExceptioninstead.

我想不出延长 的充分理由Throwable。延长ExceptionRuntimeException代替。

EDIT- In response to the OP's proposed scenario #1.

编辑- 响应 OP 提出的场景 #1。

Exceptions are a very expensive way of dealing with "normal" flow control. In some cases, we are talking thousandsof extra instructions executed to create, throw and catch an exception. If you are going to ignore accepted wisdom and use exceptions for non-exceptional flow control, use an Exceptionsubtype. Trying to pretend something is an "event" not an "exception" by declaring is as a subtype of Throwableis not going to achieve anything.

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

However, it is a mistake to conflate an exception with an error, mistake, wrong, whatever. And there is nothing wrongwith representing an "exceptional event that is not an error, mistake, wrong, or whatever" using a subclass of Exception. The key is that the event should be exceptional; i.e. out of the ordinary, happening very infrequently, ...

但是,将异常与错误、错误、错误等等混为一谈是错误的。并没有什么错与表示“特殊事件不是错误,错,错,或任何”使用的子类Exception。关键是事件应该是例外的;即不寻常,很少发生,......

In summary, a FlyingPigmay not be an error, but that is no reason not to declare it as a subtype of Exception.

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

回答by Pascal Thivent

Is this technique of (ab)using exception valid? (Is there a name for it?)

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

In my opinion, it's not a good idea:

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

  • It violates the principle of least astonishment, it makes the code harder to read, especially if there is nothing exception-al about it.
  • Throwing exception is a very expensive operation in Java (might not be a problem here though).
  • 它违反了最少惊讶原则,它使代码更难阅读,尤其是在没有任何异常的情况下。
  • 在 Java 中抛出异常是一个非常昂贵的操作(虽然在这里可能不是问题)。

Exception should just not be used for flow control. If I had to name this technique, I would call it a code smell or an anti pattern.

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

See also:

也可以看看:

If valid, should FlyingPigextends Throwable, or is Exceptionjust fine? (even though there's nothing exceptional about its circumstances?)

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

There might be situations where you want to catchThrowableto not only catch Exceptionbut alsoErrorbut it's rare, people usually don't catch Throwable. But I fail at finding a situation where you'd like to throwa subclass of Throwable.

可能在某些情况下,您不仅要捕捉Throwable还要捕捉ExceptionError但这种情况很少见,人们通常不会捕捉Throwable。但我不寻找一种情况下,你想的子类Throwable

And I also think that extending Throwabledoes not make things look less "exception-al", they make it look worse - which totally defeats the intention.

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

So to conclude, if you really want to throw something, throw a subclass of Exception.

所以总而言之,如果你真的想抛出一些东西,抛出Exception.

See also:

也可以看看:

回答by rjw

Here is a blog post from John Rose, a HotSpot architect:

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

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

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

It is about "abusing" exceptions for flow control. Slightly different use case, but.. In short, it works really well - if you preallocate/clone your exceptions to prevent stack traces being created.

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

I think that this technique is justifiable if "hidden" from clients. IE, your FlyingPig should never be able to leave your library (all public methods should transitively guarantee not to throw it). One way to guarantee this would be to make it a checked Exception.

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

I think the only justification for extending Throwable is because you want to allow people to pass in callbacks that have catch(Exception e) clauses , and you wish your result to be ignored by them. I can just about buy that...

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

回答by Stephen Denne

The org.junit.Testannotation includes the Noneclass which extends Throwableand is used as the default value for the expectedannotation parameter.

org.junit.Test注释包括该None延伸类Throwable,并且被用作用于默认值expected注释参数。

回答by Affe

If you can justify what sets a FlyingPig apart from both Error and Exception, such that it is not suitable as a subclass of either, then there's nothing fundamentally wrong with creating it.

如果您可以证明是什么将 FlyingPig 与 Error 和 Exception 区分开来,以至于它不适合作为两者的子类,那么创建它就没有什么根本性的错误。

The biggest problem I can think of is in the pragmatic world, sometimes there are justifiable reasons to catch java.lang.Exception. Your new type of throwable is going to fly right past try-catch blocks that had every reasonable expectation of suppressing (or logging, wrapping, whatever) every possible non-fatal problem.

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

On the flipside if you're doing maintenance on an old system that is unjustifiably suppressing java.lang.Exception, you could cheat around it. (Assuming the sincere appeal for time to actually fix it properly is denied).

另一方面,如果您正在一个旧系统上进行维护,而这个旧系统毫无理由地抑制了 java.lang.Exception,那么您可能会欺骗它。(假设真诚地呼吁时间实际正确修复它被拒绝)。

回答by pkaeding

As this question has evolved, I see that I misunderstood the point of the original question, so some of the other answers are probably more relevant. I will leave this answer up here, since it may still be helpful to others who have a similar question, and find this page while searching for an answer to their question.

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

There is nothing wrong with extending Throwable to be able to throw (and handle) custom exceptions. However, you should keep the following points in mind:

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

  • Using the most specific exception possible is a great idea. It will allow the caller to handle different types of exceptions differently. The class hierarchy of the exception is important to keep in mind, so your custom exception should extend another type of Throwable that is as close to your exception as possible.
  • Extending Throwable might be too high-level. Try extending Exception or RuntimeException instead (or a lower-level exception that is closer to the reason you are throwing an exception). Keep in mind the difference between a RuntimeException and an Exception.
  • A call to a method that throws an Exception (or a subclass of Exception) will need to be wrapped in a try/catch block that is capable of dealing with the exception. This is good for cases when you expect things to go wrong, due to circumstances that may be out of your control (eg, the network being down).
  • A call to a method that throws a RuntimeException (or a subclass of it) does not need to be wrapped in a try/catch block that can handle the exception. (It certainly could be, but it doesn't need to be.) THis is more for exceptions that really shouldn't be expected.
  • 尽可能使用最具体的异常是一个好主意。它将允许调用者以不同的方式处理不同类型的异常。记住异常的类层次结构很重要,所以你的自定义异常应该扩展另一种尽可能接近你的异常的 Throwable 类型。
  • 扩展 Throwable 可能太高级了。尝试扩展 Exception 或 RuntimeException (或更接近于您抛出异常的原因的较低级别的异常)。请记住 RuntimeException 和 Exception 之间的区别。
  • 对抛出异常(或异常的子类)的方法的调用需要包含在能够处理异常的 try/catch 块中。这适用于由于可能无法控制的情况(例如,网络中断)而导致事情出错的情况。
  • 对抛出 RuntimeException(或其子类)的方法的调用不需要包装在可以处理异常的 try/catch 块中。(当然可以,但不一定是。)这更适用于真正不应该预期的异常。

So, suppose you have the following exception classes in your code base:

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

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

And some methods

还有一些方法

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() { ... } 

Now, you could have some code that calls these methods like this:

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

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

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

baz();

Note that when we call bar(), we can just catch Pig, since both FlyingPigand Swineextend Pig. This is useful if you want to do the same thing to handle either exception. You could, however, handle them differently:

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

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

回答by Abhinav Sarkar

The Play! frameworkuses something like this for request handling. The request processing goes through many layers (routing, middleware, controllers, template rendering) and at the final layer the rendered HTML is wrapped in a throwableand thrown, which the top most layer catches, unwraps and sends to the client. So none of the methods in the many layers involved need to explicitly return a response object, nor do they need to have a response object passed as argument to be propagated and modified.

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

I am bit sketchy on details. You can look through the code of Play! framework for details.

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