简体   繁体   中英

Stack Overflow error on Iterating through linked list

I am not 100% sure why I am getting a stack overflow error on running the traverse() method on my linked list implementation. If i comment out the traverse() method, the program runs fine.

I double checked by iterating through using the size variable and creating a counter variable inside the traverse method, but I still get a stack overflow error.

eg

@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 class

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++;
    }

}

List interface

public interface List<T> extends Iterable<T> {
    void add(T data);
    void remove(T data);
    void removeFirst();
    void traverse();
    int size();
}

Main method

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());

    }
}

Below is the stack trace

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)

Thank you for the help. After discovering the recursive call to the toString() method, I rewrote my toString() method in the Node inner class to the following. I am wondering: are null checks in toString() a bad idea? Since it does contain an extra bit of work, making repeated calls to it a bit pricey.

@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();
    }

Log on console

Node{ data=30, nextNode=20}
Node{ data=20, prevNode=30, nextNode=10}
Node{ data=10, prevNode=20}

Your Node 's toString() method tries to add both its neighbours to a string. That means it calls toString() on both its neighbours. Then both of those try and call toString() on both their neighbours, including the Node you started with. This is an infinite recursion.

To avoid it, don't include neighbouring nodes' string representations in your node's toString() method.

The issue is with your Node's toString method, as it keeps printing the nodes back and forth, recursively:

@Override
public String toString() {
    return "Node{" +
            "data=" + data +
            ", prevNode=" + prevNode +
            ", nextNode=" + nextNode +
            '}';
}

whenever you call the prevNode to be concatenated in the String, its toString will be called. On it, the initial node will be asked to print again, because it will be the nextNode of your prevNode , which will have the code enter an infinite recursion, blowing up the Stack call limit.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM