在Java中捕获StackOverflowError是否安全?

时间:2015-02-16 22:54:38

标签: 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);
  }
} 

2 个答案:

答案 0 :(得分: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);
}

答案 1 :(得分:3)

嗯,这是一个意见问题。但是,我不相信你应该这样做。首先,你的逻辑是扭曲异常处理的意义(例外&#34;异常&#34;不是逻辑),其他程序员在解释你的代码时会遇到问题。

除此之外,你不应该抓住&#34; Erros&#34;这表明环境的运行时问题。你应该问自己是否值得忘记一些好的做法。也许,您可以尝试调整运行时配置以适应应用程序或添加额外的验证逻辑......您的呼叫在那里......但是考虑到安全性,您实际上不能说您没事,因为我们不知道如何现在堆栈状态,不同的JRE实现可能会有所不同..

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

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

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

有理由被称为错误而非异常...

来自docs:

  

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

     

公共类错误   延伸Throwable:   Error是Throwable的子类,表示合理的应用程序不应该尝试捕获的严重问题。大多数此类错误都是异常情况。 ThreadDeath错误,虽然&#34;正常&#34; condition,也是Error的子类,因为大多数应用程序不应该尝试捕获它。   一个方法不需要在其throws子句中声明在执行方法期间可能抛出但未捕获的任何Error类,因为这些错误是永远不应发生的异常情况。也就是说,出于编译时异常检查的目的,Error及其子类被视为未经检查的异常。