java - 使用Throwable进行异常以外的操作

标签 java stack throw throwable

我总是在错误的上下文中看到Throwable/Exception。但是我可以想到这样的情况:扩展Throwable只是为了打破堆栈的递归方法调用会非常好。举例来说,您正在尝试通过递归搜索的方式在树中查找并返回一些对象。一旦找到它,将其粘贴在Carrier extends Throwable中,然后将其抛出,并在调用递归方法的方法中将其捕获。

肯定的:您不必担心递归调用的返回逻辑;既然找到了所需的内容,为什么还要担心如何将引用带回方法堆栈。

负数:您有不需要的堆栈跟踪。 try/catch块也变得违反直觉。

这是一个愚蠢的简单用法:

public class ThrowableDriver {
    public static void main(String[] args) {
        ThrowableTester tt = new ThrowableTester();
        try {
            tt.rec();
        } catch (TestThrowable e) {
            System.out.print("All good\n");
        }
    }
}

public class TestThrowable extends Throwable {

}

public class ThrowableTester {
    int i=0;

    void rec() throws TestThrowable {
        if(i == 10) throw new TestThrowable();
        i++;
        rec();
    }
}

问题是,有没有更好的方法来达到相同的目的?此外,以这种方式做事有天生的坏处吗?

最佳答案

实际上,在某些情况下使用异常是一个绝妙的主意,在这种情况下,“普通”程序员不会考虑使用它们。例如,在启动“规则”并发现它不起作用的解析器中,异常是一种非常不错的方法,可以快速恢复到正确的恢复点。 (这与您建议递归递归的程度类似。)
有一个经典的反对意见,即“异常(exception)并不比goto更好”,这显然是错误的。在Java和大多数其他相当现代的语言中,您可以嵌套嵌套的异常处理程序和finally处理程序,因此,当通过异常转移控制权时,精心设计的程序可以执行清除操作,等等。要返回代码,因为必须使用返回代码,因此必须在每个返回点处添加逻辑以测试返回代码,并在退出例程之前找到并执行正确的finally逻辑(也许是几个嵌套的片段)。对于异常处理程序,这是通过嵌套异常处理程序自动实现的。
某些“行李”确实有异常(exception)-Java中的堆栈跟踪,例如。但是Java异常实际上非常有效(至少与某些其他语言的实现相比),因此,如果您没有过多使用异常,那么性能就不会成为大问题。
[我将补充说,我拥有40年的编程经验,并且自70年代末以来我就一直在使用异常。大约在1980年独立地“发明”了try/catch/finally(称为BEGIN/ABEXIT/EXIT)。]
一个“非法”题外话:
我认为这些讨论中经常遗漏的是,计算中的#1问题不是成本,复杂性,标准或性能,而是控制。
“控制”不是指“控制流”或“控制语言”或“运算符(operator)控制”,也不是经常使用术语“控制”的任何其他上下文。我的意思是“控制复杂性”,但不仅如此-它是“概念上的控制”。
我们都已经做到了(至少我们那些编程时间已经超过6周的人)-开始编写没有实际结构或标准(除了我们可能习惯使用的那些程序)的“简单的小程序”,不必担心它的复杂性,因为它是“简单的”并且是“可丢弃的”。但是,根据上下文的不同,“简单的小程序”可能会变成妖怪,可能是十分之一或一百个案例。
我们对其进行“概念控制”。修复一个错误会引入另外两个错误。程序的控制和数据流变得不透明。它的行为方式我们无法完全理解。
但是,按照大多数标准,这个“简单的小程序”并不那么复杂。并不是很多行代码。由于我们是熟练的程序员,很可能将其分解为“适当的”数量的子例程。通过复杂性度量算法来运行它,并且可能(由于它仍然相对较小并且是“子例程化”),因此得分不会特别复杂。
最终,保持概念上的控制是几乎所有软件工具和语言的插入力。是的,诸如汇编器和编译器之类的东西使我们生产率更高,而生产率是宣称的驱动力,但是生产率的提高很大程度上是因为我们不必忙于“无关紧要”的细节,而可以专注于我们想要的概念实现。
随着“外部子例程”的出现并越来越不受其环境的影响,概念控制方面的重大进步就出现在计算历史的早期,从而允许“关注点分离”,在此情况下,子例程开发人员无需对子例程的环境有太多了解,子例程的用户不需要对子例程的内部知识了解太多。
BEGIN/END和“{...}”的简单开发产生了类似的进步,因为即使“内联”代码也可以从“在此”和“在这里”之间的某种隔离中受益。
我们认为理所当然的许多工具和语言功能都存在并且非常有用,因为它们有助于保持对越来越复杂的软件结构的智能控制。而且,通过它如何帮助这种智能控制,可以相当准确地评估新工具或功能的实用性。
资源管理是最大的难题之一。这里的“资源”是指在程序执行过程中可能“创建”或“分配”并随后需要某种形式的释放的任何实体(对象,打开文件,分配的堆等)。 “自动堆栈”的发明是第一步–可以在“堆栈上”分配变量,然后在“分配”变量的子例程退出时自动将其删除。 (这曾经是一个非常有争议的概念,许多“权威”建议不要使用该功能,因为它会影响性能。)
但是在大多数(所有?)语言中,此问题仍然以一种或另一种形式存在。使用显式堆的语言需要“删除”您的"new"内容,例如。必须以某种方式关闭打开的文件。必须释放锁。这些问题中的一些可以被罚款(例如,使用GC堆)或书面化(引用计数或“育儿”),但是无法消除或隐藏所有这些问题。并且,尽管在简单的情况下解决此问题非常简单(例如,用new一个对象,调用使用该对象的子例程,然后用delete对其进行编码),但现实生活中很少那么简单。有一种方法可以进行十几个左右的不同调用,并在这些调用之间随机分配资源,并为这些资源提供不同的“生命周期”,这种情况并不少见。并且某些调用可能返回改变控制流的结果,在某些情况下导致子例程退出,或者可能导致围绕子例程主体的某些子集的循环。知道如何在这种情况下释放资源(释放所有正确的资源,而不释放任何错误的资源)是一个挑战,随着子例程随着时间的推移而被修改(因为任何复杂性的所有代码都是如此),它变得更加复杂。try/finally机制的基本概念(暂时忽略catch方面)很好地解决了上述问题(尽管我认为这很不完美)。对于需要管理的每个新资源或资源组,程序员都会引入try/finally块,并将释放逻辑放置在finally子句中。除了确保释放资源的实际方面之外,此方法还具有以下优点:清楚地描述了所涉及资源的“范围”,提供了“强制维护”的一种文档。
这种机制与catch机制耦合的事实有点偶然,因为在“异常(exception)”情况下,通常情况下用于管理资源的机制也相同。由于“异常”(表面上)很少见,因此明智的做法是将这种罕见的途径中的逻辑量减到最少,因为它永远不会像主线那样经过良好的测试,并且由于“概念化”错误情况对于平均而言尤其困难。程序员。
当然,try/finally有一些问题。其中的第一个是,这些块可以嵌套得非常深,以至于程序结构变得模糊不清。但这是do循环和if语句的共同问题,它等待语言设计者的启发。更大的问题是try/finally拥有catch(甚至更糟糕的是,异常(exception))包bag,这意味着它不可避免地被降级为二等公民。 (例如,除了现在不推荐使用的JSB/RET机制之外,finally甚至不存在于Java字节码中。)
还有其他方法。 IBM iSeries(或“System i”或“IBM i”或它们现在称为的任何东西)的概念是将清理处理程序附加到调用堆栈中的给定调用级别,以在关联程序返回(或异常退出)时执行)。尽管这在目前的形式上比较笨拙,但实际上并不适合Java程序中所需的精细控制级别,例如,它的确指向了潜在的方向。
而且,当然,在C++语言家族(而不是Java)中,可以将代表资源的类实例化为自动变量,并让对象析构函数在退出变量范围时提供“清除”。 (请注意,该方案在本质上实际上是在使用try/finally。)这在许多方面都是一种出色的方法,但是它需要一套通用的“清理”类或为每种不同类型定义一个新类。资源,从而创建了一个潜在的“云”,它在文本上庞大但相对没有意义。 (而且,正如我说的那样,这不是Java的当前形式的选择。)
但是我离题了。

关于java - 使用Throwable进行异常以外的操作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6893985/

相关文章:

java - 在循环内创建多个列表

c# - 在堆栈中查找最长的字符序列

c++ - 为什么栈尾为空?

c++ - 抛出异常后返回

java - 当 NumberFormatException 时如何使用有问题的输入字符串打印自定义消息

java - 如何在 Android 中读取和解析日期和时间?

Java 文件对象进入 inode ?

c# - object.ReferenceEquals 或 == 运算符?

java - Spring动态注入(inject),类工厂模式

c - 在 C 中减少堆栈负载、内存分配并轻松转换 malloc() 的返回值