我有两个不同的函数实现(例如树的大小),一个是递归的,另一个是使用显式堆栈。
递归非常快(可能是因为它不需要在堆上分配任何东西),但可能会导致某些“罕见”输入上的堆栈溢出(在树的例子中,它将出现在任何不平衡的树上)。显式版本较慢但不太可能导致堆栈溢出。
默认情况下使用递归实现是否安全,并通过执行显式实现从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);
}
}
答案 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实现可能会有所不同..
最后,对于底部的问题:这是一种不好的做法,并且不安全。
肯定存在堆栈溢出可能使应用程序不一致的情况,就像内存耗尽一样。想象一下构造一些对象然后在嵌套的内部方法调用的帮助下初始化 - 如果其中一个抛出,对象很可能处于一个不可能的状态,就像分配失败一样。但这并不意味着您的解决方案仍然不是最好的解决方案
有理由被称为错误而非异常...
来自docs:
公共抽象类VirtualMachineError 扩展错误: 抛出此异常表示Java虚拟机已损坏或已耗尽其继续运行所需的资源
公共类错误 延伸Throwable: Error是Throwable的子类,表示合理的应用程序不应该尝试捕获的严重问题。大多数此类错误都是异常情况。 ThreadDeath错误,虽然&#34;正常&#34; condition,也是Error的子类,因为大多数应用程序不应该尝试捕获它。 一个方法不需要在其throws子句中声明在执行方法期间可能抛出但未捕获的任何Error类,因为这些错误是永远不应发生的异常情况。也就是说,出于编译时异常检查的目的,Error及其子类被视为未经检查的异常。