The book is correct actually. Took me a little time to wrap my head around it since I'm also learning it for the first time but I'll explain why.
The whole reason for confusion here is the question regarding
when exactly is a String added to the String pool and what qualifies it as so?
I'll type out a couple of snippets to address the initial confusion.
The above snippet prints
==
Now lets do what the book is doing
The above snippet no longer prints
==
However, we are sure that the constant "2cfalse2" on the right side of the comparision operation is part of the String pool. Surely, there's a problem with the left part. To check our hypothesis, we call the
intern() method.
The above snippet prints
== again.
This brings us to the little nuance in the String Pool that is easily missable and is causing this behaviour.
The String pool is a set containing every unique String value interned during app execution. Interning happens automatically for all compile-time String constants (literals and fixed expressions) and also for all runtime String values where String.intern() is called.
Notice the stress on
compile-time.
Coming back to the question in the book that you referred to. The String s2 is initally an empty String and only that object is a part of the String Pool because that is the only compile-time constant. All the modifications of s2 that happen later are at runtime and hence are not part of the String pool. That is why line 26 returns false. In fact, you can see that if you run that code. You can also further solidify that understanding if you call the intern() method on s2 at line 26 in which case the code will print out
== as you said because the usage of intern() then adds it to the String Pool
at runtime.
I hope I could put the point across.