我不能100%确定为什么在链接列表实现上运行traverse()方法时出现堆栈溢出错误。如果我注释掉traverse()方法,程序运行正常。
我通过迭代使用size变量并在遍历方法中创建一个计数器变量来进行双重检查,但我仍然遇到堆栈溢出错误。
e.g。
@Override
public void traverse() {
Node<T> data = this.head; // In order traversal
int counter = 0;
while (counter < size) {
System.out.println(data.toString());
data = data.getNextNode();
counter++;
}
}
Linked List类
public class LinkedList<T extends Comparable<T>> implements List<T> {
private Node<T> head; // First element of linked list
private Node<T> tail; // Last element of linked list
private int size;
public LinkedList() {
this.size = 0;
}
/**
* TODO: Implement iterator and ForEach methods later on
* */
@Override
public Iterator<T> iterator() {
return null;
}
@Override
public void forEach(Consumer<? super T> action) {
}
@Override
public Spliterator<T> spliterator() {
return null;
}
private class Node<T> {
private T data;
private Node<T> prevNode;
private Node<T> nextNode;
public Node(T data) {
this.data = data;
}
public Node(T data, Node<T> nextNode, Node<T> prevNode) {
this.data = data;
this.nextNode = nextNode;
this.prevNode = prevNode;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public Node<T> getPrevNode() {
return prevNode;
}
public void setPrevNode(Node<T> prevNode) {
this.prevNode = prevNode;
}
public Node<T> getNextNode() {
return nextNode;
}
public void setNextNode(Node<T> nextNode) {
this.nextNode = nextNode;
}
@Override
public String toString() {
return "Node{" +
"data=" + data +
", prevNode=" + prevNode +
", nextNode=" + nextNode +
'}';
}
}
/**
* Add element to the start of the linked list
* */
@Override
public void add(T data) {
// head is the first element. If inserted at the front of this list, this will constantly change
Node<T> node = new Node<>(data, this.head, null);
if (this.head != null) {
this.head.setPrevNode(node);
}
// Temporarily set new data as the head
this.head = node;
if (this.tail == null) {
this.tail = node; // If tail is not set, make newly inserted node as tail;
}
incrementSize();
}
@Override
public void remove(T data) {
// TODO
decrementSize();
}
@Override
public void removeFirst() {
// TODO
decrementSize();
}
@Override
public void traverse() {
Node<T> data = this.head; // In order traversal
while (data != null) {
System.out.println(data.toString());
data = data.getNextNode();
}
}
@Override
public int size() {
return this.size;
}
/**
* ===================================
* Private methods here
* */
private void decrementSize() {
this.size--;
}
private void incrementSize() {
this.size++;
}
}
列表界面
public interface List<T> extends Iterable<T> {
void add(T data);
void remove(T data);
void removeFirst();
void traverse();
int size();
}
主要方法
public class App {
public static void main(String[] args) {
List<Integer> linkedList = new LinkedList<>();
linkedList.add(10);
linkedList.add(20);
linkedList.add(30);
linkedList.traverse(); // Error here
System.out.println(linkedList.size());
}
}
下面是堆栈跟踪
Exception in thread "main" java.lang.StackOverflowError
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:449)
at java.lang.StringBuilder.append(StringBuilder.java:136)
at linkedlist.practice.LinkedList$Node.toString(LinkedList.java:79)
at java.lang.String.valueOf(String.java:2994)
at java.lang.StringBuilder.append(StringBuilder.java:131)
at linkedlist.practice.LinkedList$Node.toString(LinkedList.java:79)
at java.lang.String.valueOf(String.java:2994)
at java.lang.StringBuilder.append(StringBuilder.java:131)
at linkedlist.practice.LinkedList$Node.toString(LinkedList.java:79)
at java.lang.String.valueOf(String.java:2994)
at java.lang.StringBuilder.append(StringBuilder.java:131)
at linkedlist.practice.LinkedList$Node.toString(LinkedList.java:79)
at java.lang.String.valueOf(String.java:2994)
感谢您的帮助。在发现对toString()方法的递归调用之后,我将Node内部类中的toString()方法重写为以下内容。 我想知道:toString()中的空检查是个坏主意吗?因为它确实包含了额外的工作,所以重复调用它有点贵。
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append( "Node{ data=");
sb.append(data);
if (prevNode != null) {
sb.append(", prevNode=").append(prevNode.getData());
}
if (nextNode != null) {
sb.append(", nextNode=").append(nextNode.getData());
}
sb.append('}');
return sb.toString();
}
登录控制台
Node{ data=30, nextNode=20}
Node{ data=20, prevNode=30, nextNode=10}
Node{ data=10, prevNode=20}
答案 0 :(得分:7)
您的Node
toString()
方法尝试将其邻居添加到字符串中。这意味着它会在其两个邻居上调用toString()
。然后,这两个人都尝试在他们的邻居上调用toString()
,包括你开始的Node
。这是一个无限递归。
为了避免这种情况,请不要在节点的toString()
方法中包含相邻节点的字符串表示。
答案 1 :(得分:5)
问题在于您的Node toString
方法,因为它会以递归方式来回打印节点:
@Override
public String toString() {
return "Node{" +
"data=" + data +
", prevNode=" + prevNode +
", nextNode=" + nextNode +
'}';
}
无论何时调用prevNode
在字符串中连接,都会调用其toString
。在它上面,将要求初始节点再次打印,因为它将是nextNode
的{{1}},这将使代码进入无限递归,从而炸毁堆栈调用限制。