在 Java 中捕获 StackOverflowError 是否安全?

Is it safe to catch StackOverflowError in Java?

我有两种不同的函数实现(例如树的大小),一种是递归的,另一种是使用显式堆栈的。

递归非常快(可能是因为它不需要在堆上分配任何东西)但可能会导致某些 "rare" 输入的堆栈溢出(在树的示例中,它会在任何不平衡树)。显式版本速度较慢,但​​不太可能导致堆栈溢出。

默认情况下使用递归实现并通过执行显式实现从 WhosebugError 异常中恢复有多安全?

这被认为是不好的做法吗?

这是一个小代码示例:

interface Node {
  List<? extends Node> getSons();
}

static int sizeRec (Node root) {
  int result = 1;
  for (Node son : root.getSons()) {
    result += sizeRec(son);
  }
  return result;
}

static int sizeStack (Node root) {
  Stack<Node> stack = new Stack<Node>();
  stack.add(root);
  int size = 0;
  while (! stack.isEmpty()) {
    Node x = stack.pop();
    size ++;
    for (Node son : x.getSons()) {
       stack.push(son);
    }
  }
  return size;
}

static int size (Node root) {
  try {
    return sizeRec(root);
  } catch (WhosebugError e) {
    return sizeStack(root);
  }
} 

嗯,这个见仁见智。但是,我认为你不应该那样做。首先,您的逻辑扭曲了异常处理的含义("exceptions" 的异常不是逻辑),其他程序员在解释您的代码时会遇到问题。

除此之外,您不应该捕获 "Erros",这表明 运行-time 环境问题。您应该问问自己,忘记一些好的做法是否值得。也许,您可以尝试调整 运行 时间配置以适应应用程序或放置额外的验证逻辑...您的调用..但考虑到安全性,您实际上不能说您很好,因为我们没有现在堆栈状态如何,不同的 JRE 实现可能会有所不同。

最后,对于最下面的问题:这是一种不好的做法,不安全。

引自https://softwareengineering.stackexchange.com/questions/209099/is-it-ever-okay-to-catch-Whosebugerror-in-java

surely there are situations where a stack overflow might leave an application inconsistent just like a memory exhaustion. Just imagine that some object is constructed and then initialized with the help of nested internal method calls - if one of them throws, the object may very well be in a state not supposed to be possible, just as if an allocation had failed. But that doesn't mean that your solution couldn't still be the best one

它有理由被称为错误而不是异常...

来自文档:

public abstract class VirtualMachineError extends Error: Thrown to indicate that the Java Virtual Machine is broken or has run out of resources necessary for it to continue operating

public class Error extends Throwable: An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch. Most such errors are abnormal conditions. The ThreadDeath error, though a "normal" condition, is also a subclass of Error because most applications should not try to catch it. A method is not required to declare in its throws clause any subclasses of Error that might be thrown during the execution of the method but not caught, since these errors are abnormal conditions that should never occur. That is, Error and its subclasses are regarded as unchecked exceptions for the purposes of compile-time checking of exceptions.

我建议在你的 sizeRecursive 方法中维护一个堆栈深度计数器,如果你超过指定的级别切换到 sizeStackUsingHeap 方法。不要依赖 Whosebug 异常 - 这是不好的做法。你不应该使用异常来定义你的算法。

interface Node {

    List<? extends Node> getSons();
}

// Switch to a heap stack if the stack ever hits this level.
private static final int STACKLIMIT = 1000;

private static int sizeRecursive(Node root) {
    // Start the stack depth at 0.
    return sizeRecursive(root, 0);
}

// Recursive implementation.
private static int sizeRecursive(Node root, int depth) {
    int result = 1;
    for (Node son : root.getSons()) {
        if (depth < STACKLIMIT) {
            result += sizeRecursive(son, depth + 1);
        } else {
            // Too deep - switch to heap.
            result += sizeUsingHeap(son);
        }
    }
    return result;
}

// Use this when the stack gets realy deep. It maintains the stack in the heap.
private static int sizeUsingHeap(Node root) {
    Stack<Node> stack = new Stack<>();
    stack.add(root);
    int size = 0;
    while (!stack.isEmpty()) {
        // I am assuming this algorithm works.
        Node x = stack.pop();
        size++;
        for (Node son : x.getSons()) {
            stack.push(son);
        }
    }
    return size;
}

// Always use sizeRecursive to begin with.
public static int size(Node root) {
    return sizeRecursive(root);
}