11

I have understood from this and this answer, the possible scenarios where your code could get into an infinite loop resulting into stackOverFlowError but I don't understand how the same scenario is getting replicated here,

public class A {
  private B b = new B();
  @Override
  public String toString() {
      return "b "+b;
  }
}

public class B {
  private A a = new A();
  @Override
  public String toString() {
      return "";
  }
}

public class StackoverflowErrorTest {
  public static void main(String[] args) {
     A a = new A();
     System.out.println(a);
  }
}

This code is generating below stack trace:-

Exception in thread "main" java.lang.StackOverflowError
at stackoverflowerror.B.<init>(B.java:5)
at stackoverflowerror.A.<init>(A.java:5)
.
.
.

As per my understanding, When I am printing object 'a' in the main method, it will invoke the toString method of A class, in that method I am returning the object of B class which would implicitly call the toString method of B class. Now, in the toString method of B class, all I am returning is an empty string. Then how and where is the scope of infinite loop coming into picture here. Please explain.

Rahul Gupta
  • 1,079
  • 2
  • 15
  • 27
  • Feel free to comment both ```toString()``` methods, just to see if they have anything to do with the problem. – tevemadar Jul 27 '18 at 10:56
  • I couldn't find a Stack Overflow duplicate. But here is a Quora dupe: https://www.quora.com/Why-does-the-following-Java-program-throw-a-stack-overflow-error – Tim Biegeleisen Jul 27 '18 at 10:57

6 Answers6

10

The problem is not with toString, but with private B b = new B(); and private A a = new A();.

You are creating A which creates B which creates A which creates B and so on

And stacktrace says exact same thing: B.<init> means initializer, not toString. It also points you to the lines throwing exception: B.java:5 and A.java:5

Live code: https://ideone.com/H8wwOR

If you really need to hold A in B and B in A you could either pass them using constructor parameters or via setter methods:

class A {
    private B b;

    public A(B b) {
        this.b = b;
    }
}

class B {
    private A a;

    public A getA() {
        return this.a;
    }

    public void setA(A a) {
        this.a = a;
    }
}

And then use them like:

B b = new B();
A a = new A(b);
b.setA(a);

This is a simplified example so you can get idea. In large applications you might want to add builders, field / constructor parameters injection or factories if needed.

awesoon
  • 32,469
  • 11
  • 74
  • 99
6

It enters the endless cycle of events. The method toString() is innocent in this case:

  1. The method main creates an instance of the class A
  2. The class A creates an instance of the class B upon initialization
  3. The class B creates an instance of the class A upon initialization
  4. Go to the step 2

The local variables are allocated on the stack and the StackOverflowError is a result of a bad recursive call.

Thrown when a stack overflow occurs because an application recurses too deeply.

Recommended solution, remove the reference to the class A in the class B because it's unused:

public class B {

    @Override
    public String toString() {
        return "";
    }
}
Nikolas Charalambidis
  • 40,893
  • 16
  • 117
  • 183
2

When in your main you call A a = new A(); , the code will call the constructor of A, which will call private B b = new B(); , which will call private A a = new A(); and so on for infinite loop :)

The problem is not related to toString() at all

Leviand
  • 2,745
  • 4
  • 29
  • 43
0

Every A contains a new B in its b field. And every B contains a new A in its a field.

Creating a new A automatically creates a new B, and creating that automatically creates another new A, and another new B, and so on until the stack overflows.

khelwood
  • 55,782
  • 14
  • 81
  • 108
0

Your A contains a B which contains an A which contains a B which contains an A which contains a B which contains an A which contains a B which contains an A which contains a B which contains an A which contains a B ....

Your A in B isn't used so you can remove it.

Note: the stack trace shows the problem is in construction ie () and nothing to do with your toString();

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
0

In this scenario, whenever you are creating an object of class A (or B) it also creates a new object of class B(or A in case of B) whenever instantiated. Now this is a never ending cycle as whenever a new object is created of either class A or B, again a new object of another class is created as well resulting in an infinite cycle of object creation. Therefore, throwing the java.lang.StackOverflowError exception.

Vaibhav Singh
  • 177
  • 10