简体   繁体   English

在Java中扩展Throwable

[英]Extending Throwable in Java

Java lets you create an entirely new subtype of Throwable , eg: 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 really tempted to do something like this has the following properties: 真的想做这样的事情的场景具有以下属性:

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

So perhaps it's clear now what I'm trying to say: FlyingPig is the result of 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 isFound flag may be more expensive, because it has to be checked continuously throughout the search process, most likely at every level of the recursion. 事实上,传统的控制流构造使用boolean isFound标志的替代方案可能更昂贵,因为必须在整个搜索过程中连续检查,最有可能在递归的每个级别。 This check will fail 99.99% of the time, but it's absolutely necessary to propagate the termination condition. 此检查将在99.99%的时间内失败,但绝对有必要传播终止条件。 In a way, while effective , the check is inefficient ! 在某种程度上,虽然有效 ,但检查效率低下

By simply throw -ing a FlyingPig when the sought object is found, you don't have to clutter the code with the management of the boolean isFound flag. 通过在找到所寻找的对象时简单地throw一个FlyingPig ,您不必通过管理boolean isFound标志来FlyingPig代码。 Not only is the code cleaner in that regard, but it may run faster due to this omission. 在这方面,代码不仅更清洁,而且由于这种遗漏,它可能运行得更快。

So to summarize, the choice is between these two: 总而言之,选择是在这两者之间:

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

Questions: 问题:

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

I'd say that it is a really bad idea. 我会说这是一个非常糟糕的主意。 A lot of code is implemented on the assumption that if you catch Error and Exception you have caught all possible exceptions. 很多代码都是在假设你捕获ErrorException你已经捕获了所有可能的异常。 And most tutorials and textbooks will tell you the same thing. 大多数教程和教科书都会告诉你同样的事情。 By creating a direct subclass of Throwable you are potentially creating all sorts of maintenance and interoperability problems. 通过创建Throwable的直接子类,您可能会创建各种维护和互操作性问题。

I can think of no good reason to extend Throwable . 我认为没有充分理由延长Throwable Extend Exception or RuntimeException instead. 而是扩展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 thousands of 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 Exception subtype. 如果您要忽略已接受的智慧并使用异常进行非异常流控制,请使用Exception子类型。 Trying to pretend something is an "event" not an "exception" by declaring is as a subtype of Throwable is not going to achieve anything. 试图伪装某事是一个“事件”,而不是一个“例外”,声称作为Throwable的子类型不会实现任何目标。

However, it is a mistake to conflate an exception with an error, mistake, wrong, whatever. 但是,将异常与错误,错误,错误等混淆是错误的。 And there is nothing wrong with representing an "exceptional event that is not an error, mistake, wrong, or whatever" using a subclass of Exception . 并没有什么不妥表示“特殊事件不是错误,错,错,或任何”使用的子类Exception The key is that the event should be exceptional ; 关键是该活动应该是特殊的 ; ie out of the ordinary, happening very infrequently, ... 即不寻常的,很少发生,......

In summary, a FlyingPig may not be an error, but that is no reason not to declare it as a subtype of Exception . 总之, FlyingPig可能不是错误,但没有理由不将它声明为Exception的子类型。

Is this technique of (ab)using exception valid? (ab)这种技术使用异常是否有效? (Is there a name for it?) (有名字吗?)

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 FlyingPig extends Throwable , or is Exception just fine? 如果有效, FlyingPig应该扩展Throwable ,还是Exception就好了? (even though there's nothing exceptional about its circumstances?) (即使它的情况没有什么特别之处?)

There might be situations where you want to catch Throwable to not only catch Exception but also Error but it's rare, people usually don't catch Throwable . 在某些情况下,您可能希望捕获 Throwable ,不仅可以捕获Exception ,还可以捕获Error但是很少见,人们通常不会捕获Throwable But I fail at finding a situation where you'd like to throw a subclass of Throwable . 但是我找不到你想抛出 Throwable的子类的情况。

And I also think that extending Throwable does 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: 也可以看看:

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_considered_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). IE,你的FlyingPig永远不能离开你的图书馆(所有公共方法都应该保证不丢弃它)。 One way to guarantee this would be to make it a checked Exception. 保证这一点的一种方法是使其成为一个经过检查的例外。

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. 我认为扩展Throwable的唯一理由是因为你想允许人们传递具有catch(Exception e)子句的回调,并且希望你的结果被他们忽略。 I can just about buy that... 我只能买那个......

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

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

On the flipside if you're doing maintenance on an old system that is un justifiably suppressing java.lang.Exception, you could cheat around it. 另一方面,如果你正在对一个理由压制java.lang.Exception的旧系统进行维护,你可能会欺骗它。 (Assuming the sincere appeal for time to actually fix it properly is denied). (假设真正呼吁时间真正正确地修复它被拒绝)。

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. 将Throwable扩展为能够抛出(和处理)自定义异常没有任何问题。 However, you should keep the following points in mind: 但是,您应该记住以下几点:

  • 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. 异常的类层次结构非常重要,因此您的自定义异常应该扩展另一种类型的Throwable,尽可能接近您的异常。
  • Extending Throwable might be too high-level. 扩展Throwable可能太高级了。 Try extending Exception or RuntimeException instead (or a lower-level exception that is closer to the reason you are throwing an exception). 尝试改为扩展Exception或RuntimeException(或者更接近抛出异常的原因的低级异常)。 Keep in mind the difference between a RuntimeException and an Exception. 请记住RuntimeException和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. 对抛出异常(或Exception的子类)的方法的调用将需要包装在能够处理异常的try / catch块中。 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. 对抛出RuntimeException(或其子类)的方法的调用不需要包装在可以处理异常的try / catch块中。 (It certainly could be, but it doesn't need to be.) THis is more for exceptions that really shouldn't be expected. (当然可以,但不一定是这样。)对于真正不应该被期待的异常,这更多。

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 FlyingPig and Swine extend Pig . 请注意,当我们调用bar() ,我们可以捕获Pig ,因为FlyingPigSwine扩展了Pig This is useful if you want to do the same thing to handle either exception. 如果你想做同样的事情来处理任何一个异常,这很有用。 You could, however, handle them differently: 但是,您可以采用不同的方式处理它们:

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

The Play! 玩! framework uses 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 throwable and thrown, which the top most layer catches, unwraps and sends to the client. 请求处理经历了许多层(路由,中间件,控制器,模板呈现),并且在最后一层,呈现的HTML被包装在throwable和thrown中,最顶层的层捕获,解包并发送到客户端。 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. 因此,涉及的许多层中的所有方法都不需要显式返回响应对象,也不需要将响应对象作为参数传递以进行传播和修改。

I am bit sketchy on details. 我对细节有点粗略。 You can look through the code of Play! 你可以浏览Play的代码! framework for details. 细节框架。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM