在Java中捕获StackOverflowError是否安全?

Mar*_*arc 6 java stack-overflow

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

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

默认情况下使用递归实现是否安全,并通过执行显式实现从StackOverflowError异常中恢复?

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

这是一个代码的小例子:

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 (StackOverflowError e) {
    return sizeStack(root);
  }
} 
Run Code Online (Sandbox Code Playgroud)

Old*_*eon 6

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

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);
}
Run Code Online (Sandbox Code Playgroud)


Vic*_*tor 3

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

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

最后,对于底部的问题:这是一种不好的做法,而且不安全。

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

当然,在某些情况下,堆栈溢出可能会导致应用程序不一致,就像内存耗尽一样。想象一下,在嵌套的内部方法调用的帮助下构造并初始化某个对象 - 如果其中一个抛出异常,则该对象很可能处于一种不应该发生的状态,就像分配失败一样。但这并不意味着您的解决方案仍然是最好的解决方案

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

来自文档:

公共抽象类VirtualMachineError扩展了错误:抛出该异常表示Java虚拟机已损坏或已耗尽继续运行所需的资源

public class Error extends Throwable:Error 是 Throwable 的子类,它指示合理的应用程序不应尝试捕获的严重问题。大多数此类错误都是异常情况。ThreadDeath 错误虽然是“正常”情况,但也是 Error 的子类,因为大多数应用程序不应尝试捕获它。方法不需要在其 throws 子句中声明在方法执行期间可能抛出但未被捕获的任何 Error 子类,因为这些错误是永远不应该发生的异常情况。也就是说,Error 及其子类被视为未经检查的异常,以便进行异常的编译时检查。