I was studying Kathy Sierra Java book. I came across one question something like this:
public class A {
public static void main(String args[]){
S
s2
is a reference to s1
in the first case. In the second case, +
is translated to s1.concat("d")
which creates a new string, so the references s1
and s2
point to different string objects.
In the case of StringBuffer
, the reference never changes. append
changes the internal structure of the buffer, not the reference to it.
Immutable scenario
The String
class and wrapper classes like Integer
and Double
are all immutable. This means that when you do something like:
1. String s1 = "a";
2. s2 = s1;
3. s1 = s1 + "b";
4. System.out.println(s1 == s2); // prints false
Notice what really happens under the covers (very simplified, and using bogus memory addresses):
"a"
at memory address 0x000001
.s1
to 0x000001
so that it effectively points to string "a"
.s1
and set it to s2
. So now both s1
and s2
have the same value of 0x000001
, and so both point to string "a"
.s1
is pointing to (string "a"
), and use that to create a new and distinct string of "ab"
that will live at a different memory address of 0x000002
. (Note that string "a"
remains unchanged at memory address 0x000001
).0x000002
to variable s1
so that it now effectively points to this new string "ab"
.s1
and s2
, which are now at 0x000002
and 0x000001
respectively. Clearly, they don't have the same values (memory addresses) so the result is false
.false
to the console.So you see, when changing the "a"
string to a "ab"
string, you were not modifying the "a"
string. Rather, you were creating a 2nd distinct string with the new value of "ab"
, and then changing a reference variable to point to this newly created string.
The exact same pattern occurs when coding with the other classes like Integer
or Double
, which are immutable as well. You have to understand that when you use operators like +
or -
on instances of these classes, you are not modifying the instance in any way. Rather, you are creating a whole new object, and getting a new reference to that new object's memory address that you can then assign to a reference variable.
Mutable scenario
This is in complete contrast to mutable classes like StringBuffer
or StringBuilder
, and others like the unfortunate java.util.Date
. (BTW, it's best you get in the habit of using StringBuilder
instead of StringBuffer
, unless you are using it on purpose for a multi-threaded requirement)
With mutable classes, the exposed methods of these classes do change (or mutate) the internal state of the object, instead of creating a whole new object. As a result, if you have multiple variables pointing to the same mutable object, if one of those variables is used to access the object and make changes to it, accessing that same object from any of the other variables will see the changes as well.
So if we take this code, for instance (again, please use StringBuilder
instead, the net result will be the same):
1. StringBuffer sb = new StringBuffer("a");
2. StringBuffer sb2 = sb;
3. sb.append("b");
4. System.out.println(sb == sb2); // prints true
Notice how different this is handled internally (again, very simplified, even omitting some details to keep it simple and understandable):
StringBuffer
instance at memory address 0x000001
with an internal state of "a"
.sb
to 0x000001
so that it effectively points to the StringBuffer
instance, which itself contains "a"
as part of its state.sb
and set it to sb2
. So now both sb
and sb2
have the same value of 0x000001
, and so both point to the same StringBuffer
instance.sb
is pointing to (the StringBuffer
instance), and call the .append()
method on it to ask it to mutate its state from "a"
to "ab"
. (Very important!!! Unlike the immutable version, the memory address of sb
does NOT change. So both sb
and sb2
are still pointing to the same StringBuffer
instance.sb
and sb2
, which are both still at 0x000001
. This time, they both have the same value, so the result is true
.true
to the console.Bonus consideration: ==
vs. equals()
Once you understand the above, then you now have the required knowledge to better understand this peculiar scenario:
1. String s1 = "abc";
2. String s2 = new String(s1);
3. System.out.println(s1 == s2); // prints false?!?
4. System.out.println(s1.equals(s2)); // prints true
Surprisingly, line 3 returns false
(?!?). However, once we understand what the ==
operator is comparing, combined with a better understanding of immutable classes like String
, then it's actually not that hard to understand, and it teaches us a valuable lesson.
So if we do the exercise again of examining what is really happening, we find the following:
"abc"
at memory address 0x000001
.s1
to 0x000001
so that it effectively points to string "abc"
."abc"
at memory address 0x000002
. (Note that we now have 2 strings "abc"
. One at memory address 0x000001
, and the other one at 0x000002
).s2
to 0x000002
so that it effectively points to the 2nd string "abc"
.s1
and s2
, which are now at 0x000001
and 0x000002
respectively. Clearly, they don't have the same values (memory addresses) so the result is false
. (Even though they are both pointing to strings that are the same logically speaking, in memory, they are still 2 distinct strings!)false
to the console..equals()
on the string pointed to by variable s1
(address 0x000001
). And as a parameter, pass a reference to the string pointed to by variable s2
(address 0x000002
). The equals
method compares the values of both strings, and determines that they are logically equal, so it returns true
.true
to the console.Hopefully, the above now makes sense to you.
And the lesson?
==
is not the same as equals()
.
==
will blindly check to see if the variables' values are the same. In the case of reference variables, the values are memory address locations. So, even if 2 variables point to logically equivalent objects, if they are different objects in memory, it will return false.
equals()
is meant to check for logical equality. What that means exactly depends on the specific implementation of the equals()
method that you invoke. But in general, this is the one that returns the result we expect intuitively, and is the one you want to use when comparing strings to avoid nasty unexpected surprises.
If you need more information, I recommend you do further searches on the topic of immutable vs mutable classes. And also on the topic of value vs. reference variables.
I hope this helps you.