== and equals for strings
A variable of type String, like an array variable, holds a reference. This has a consequence that surprises almost every beginner: two strings with the same text are compared with ==, and the answer is false:
public class Main {
public static void main(String[] args) {
String a = "hi";
String b = "Hi".toLowerCase();
System.out.println(b); // hi
System.out.println(a == b); // false
System.out.println(a.equals(b)); // true
}
}a and b both say the same thing, hi. Why does == answer false while equals answers true?
Back to the cards. Imagine two cards with the same word printed on them. You can ask two different questions about them:
- "Is this the same card?" is what
==asks; - "Do the cards say the same thing?" is what
equalsasks.
For two different cards with the same word, the answers differ: no to the first question, yes to the second.
What == compares
The == operator compares references, as it does for arrays. It gives true only when both variables lead to one object, and it does not look at the characters.
In the program above, toLowerCase() created a new string hi: it is a separate object, and a leads to a different one. That is why a == b gives false, even though the characters match.
The equals method asks the second question: it compares the characters of two strings in order, the way Arrays.equals compares the elements of arrays. It does not care whether there is one object or two.
The string pool
Then why does == sometimes work? With arrays, every {9, 2, 3} written in the code creates a new array, but Java does not multiply equal string literals.
The string pool is a shared rack in memory where string literals hang, that is, text in quotes written right in the code. Each text hangs on it once, and all equal literals in a program lead to one card.
Strings created while the program runs do not go onto the rack. That is how toLowerCase() gets its string, and so does new String("hi"). The word new always creates a new object, even if the same text already hangs on the rack.
Here are three lines of code with both cases: String a = "hi";, String b = "hi"; and String c = new String("hi");. The widget draws the variables and the heap, as in the arrays theme; the string pool is part of the heap. Run the lines one at a time and watch which object each variable leads to. Then press each comparison.
1String a = "hi";2String b = "hi";3String c = new String("hi");
The program has not started yet. Press the button to run the first line.
Let's see what we got:
a == bgivestrue: both"hi"literals are one card from the rack.a == cgivesfalse:new String("hi")created a second card, andcleads to it.a.equals(c)givestrue: both cards sayhi.
That is exactly why == is dangerous. On literals it answers true and seems to work, while on a string created at run time it silently gives false. And when a method has nothing to change, it may return the same string, and then "hi".toLowerCase() == "hi" gives true.
The rack picture is off here. You can hang two identical cards on a real rack. The pool keeps each text once, and that is safe only because nobody can change a string.
Compare with equals
There is one rule: the contents of strings are compared with equals. To compare without regard to letter case, there is equalsIgnoreCase:
String answer = "YES".toLowerCase();
System.out.println(answer.equals("yes")); // true
System.out.println(answer == "yes"); // false
System.out.println("Yes".equalsIgnoreCase("YES")); // trueanswer.equals("yes")compares the characters and givestrue.answer == "yes"givesfalse: the stringanswerwas created at run time, while"yes"is a literal from the pool.equalsIgnoreCasetreatsYandyas the same and givestrue.
How == differs from equals is one of the most frequent questions in a Java interview. A good answer has three parts:
==compares references, andequalscompares contents;- equal literals are one object from the pool, so
==givestruefor them; new Stringand strings created at run time are separate objects, so strings are always compared withequals.
Common mistake
A command is compared with a literal using ==:
String command = "EXIT".toLowerCase();
if (command == "exit") { // ✗
System.out.println("bye");
}The program prints nothing and reports no error. command holds exit, but it is a new object, not the literal from the pool, so == gives false. The compiler lets this code through. Write command.equals("exit").
Key points
- For strings,
==compares references, andequalscompares characters. - Equal literals lead to one object from the string pool, so
==givestruefor them. new String("hi")and strings that methods create at run time are new objects, and==against a literal givesfalsefor them.- The contents of strings are compared with
equals, and without regard to case withequalsIgnoreCase.