The concept of immutable can easily be explained with this example
String s1 = "Hello";
String s2 = "Hi";
String s3 = "Hello";
if (s1 == s2){ System.out.println("s1==s2");}
if (s1 == s3){ System.out.println("s1==s3");}
s1 = "Hi";
if (s1 == s2){ System.out.println("s1==s2");}
if (s1 == s3){ System.out.println("s1==s3");}
If you execute this piece of code you would get
s1==s3
and s1==s2
. What does this example explains?
When you created s1, the compiler created a string "Hello" in its string table (i dun remember its exact name). When you created s2, it create new object "Hi". Now when you created s3, compiler knows that there is an object "Hello" already in its string table, so why not just refer s3 to it. So s1=s3 (memory vise). Same happened when you assigned value "hi" to s1, compiler could see that "hi" is already in the memory being pointed by s3, so it pointed it to s1 as well.
In case of StringBuffer, Compiler allocates memory to the object, which you can manipulate rather "pool" as in case of String.