Playing with the ==
and with the pool strings
Java uses a mechanism called String interning, putting the strings in a pool to try to store only one copy of each string in memory.
When Java finds String literals in the code, it always returns the same String instance, which points to an input in the pool internal JVM. It is therefore possible to use the ==
to compare two variables that receive String literals:
String literal = "str";
String outraLiteral = "str";
System.out.println(literal == outraLiteral); //exibe true
Even, as Java treats String literals as instances it is possible to compare a literal directly, thus:
System.out.println(literal == "str"); //também retorna true
On the other hand, we cannot rely on the comparison operator when we do not know how the String was created, since it is possible to create other instances in various ways. Example:
String novaInstancia = new String("str");
System.out.println("str" == novaInstancia); //retorna false
The above code creates a new String instance, which is not the same returned by the JVM for the literal "str"
.
But, however, in the meantime, that doesn’t mean we have two entries from "str"
in the pool Java. How can we verify this? Using the method String.intern()
, that returns a reference to the String that is in pool. Example:
String novaInstancia = new String("str");
System.out.println("str" == novaInstancia.intern()); //retorna true
Applying this to the question example, we would have:
public class TesteString {
public static void main(String[] args) {
String str1 = "teste";
String str2 = "Oteste".substring(1);
System.out.println("str1: " + str1 + ", str2: " + str2);
if(str1 == str2.intern()) {
System.out.println("str1 igual a str2");
}
else {
System.out.println("str1 diferente de str2");
}
}
}
And the result:
str1: test, str2: test
str1 equals str2
All very interesting. But, what if we created a string in a dazzling way?
StringBuilder sb = new StringBuilder();
sb.append('s');
sb.append('t');
sb.append('r');
System.out.println("str" == sb.toString().intern()); //continua sendo true
But what about the equals()
?
If the comparison with ==
is faster than the method equals()
, we must abandon the equals()
and use the intern()
everywhere? The answer is nay.
Not all Strings are internalized in pool immediately. When we call the method intern()
, if it is not there, then Java will add it. The problem is that once in pool String goes to permanent memory and will no longer be collected by Garbage Collector.
When you want speed and the set of values is relatively small, use the method intern()
may be advantageous. But if we use this feature, for example, for processing text, XML, databases, we will soon see a OutOfMemoryError
.
Also, add a Strings in pool
can also be an "expensive" operation. In addition to being required to check if the String already exists, Java will probably have to handle concurrent accesses.
And finally, a major drawback is the code becoming more prone to bugs (error prone), since it is necessary that the developer always put the intern()
when necessary.
Other forms of comparison
Going a little beyond the exact comparison of Strings, we have other interesting ways of comparison:
Case insensitive (without regard to upper and lower case)
System.out.println("STR".equalsIgnoreCase("str")); //retorna true
A string contained in another
System.out.println("###STR###".contains("STR")); //retorna true
Which string is "bigger" than the other?
System.out.println("str1".compareTo("str2")); //retorna -1, pois "str1" é menor que "str2"
Or:
System.out.println("str1".compareToIgnoreCase("STR2")); //retorna -1, ignorando a capitalização
The method compareTo
returns:
1
if the first string is larger than the second string
0
if they are equal
-1
if the first string is smaller than the second string
Begins with...
System.out.println("str1".startsWith("str")); //returna true, pois "str1" começa com "str"
Ends with...
System.out.println("str1".endsWith("r1")); //return true, pois "str1" termina com "r1"
Regular expression
System.out.println("str2".matches("\\w{3}\\d")); //return true, pois corresponde à expressão regular
Is empty?
String str1 = "";
System.out.println(str1.isEmpty());
System.out.println(str1.length() == 0);
System.out.println(str1.equals(""));
Particularly I prefer the first method for Java >= 6 and the second for previous versions.
A real lesson on string comparison. Congratulations!
– Geison Santos