Have you ever wondered how Java's String methods, keywords, and operators process comparisons in a String pool? Here's your chance to find out!
Java’s String
class encapsulates an array of bytes
. A byte can be converted to a char
, in which case, String
becomes an array of characters used to compose words, sentences, or any other data you want.
In this Java challenger, you’ll learn how to compare two String
s for equality. Comparing String
s is relatively simple, but it’s helpful to know a few additional programming concepts and techniques before you start.
Comparing Strings in Java
- Encapsulation and Strings
- Method overloading and Strings
- String pools
- String pools and intern()
- Using equals() with the String class
- Other common String methods
Encapsulation and Strings
Encapsulation is one of the most powerful concepts in object-oriented programming. Because of encapsulation, you don’t need to know how the String class works; you just need to know what methods to use on its interface.
When you look at the following String
class in Java, you can see how the array of char
s is encapsulated:
public String(char value[]) {
this(value, 0, value.length, null);
}
To understand encapsulation better, consider a car. Do you need to know how the car works under the hood in order to drive it? Of course not, but you do need to know what to do with the car’s interfaces, things like the accelerator, brakes, and steering wheel. Each of these interfaces supports certain actions: accelerate, brake, turn left, turn right, etc. It’s the same in object-oriented programming.
Method overloading and the String class
Method overloading is a technique the String
class uses extensively. Overloading can make your classes really flexible, including String
:
public String(String original) {}
public String(char value[], int offset, int count) {}
public String(int[] codePoints, int offset, int count) {}
public String(byte bytes[], int offset, int length, String charsetName) {}
// And so on…...
Rather than trying to understand how the String
class works, this article will help you understand what String
does and how to use it in your code.
String pools
String
is possibly the most-used class in Java. If a new object was created in the memory heap everytime we used a String
, we would waste a lot of memory. The String
pool solves this problem by storing just one object for each String
value, as shown here:
Although we created a String
variable for the Duke
and Juggy
String
s, only two objects are created and stored in the memory heap. For proof, look at the following code sample. (Recall that we use the “==
” operator in Java to compare two objects and determine whether they are the same.)
String juggy = "Juggy";
String anotherJuggy = "Juggy";
System.out.println(juggy == anotherJuggy);
This code will return true
because the two String
s point to the same object in the String
pool. Their values are the same.
The ‘new’ operator
This code looks similar to the previous example, but there is a difference:
String duke = new String("duke");
String anotherDuke = new String("duke");
System.out.println(duke == anotherDuke);
Based on the previous example, you might expect this code would return true
, but it’s actually false
. Adding the new
operator forces the creation of a new String
in the memory heap. Thus, the JVM will create two different objects.
The intern() method
To store a String
in a String
pool, we use a technique called String interning. Here’s what Javadoc tells us about the intern()
method:
/**
* Returns a canonical representation for the string object.
*
* A pool of strings, initially empty, is maintained privately by the
* class {@code String}.
*
* When the intern method is invoked, if the pool already contains a
* string equal to this {@code String} object as determined by
* the {@link #equals(Object)} method, then the string from the pool is
* returned. Otherwise, this {@code String} object is added to the
* pool and a reference to this {@code String} object is returned.
*
* It follows that for any two strings {@code s} and {@code t},
* {@code s.intern() == t.intern()} is {@code true}
* if and only if {@code s.equals(t)} is {@code true}.
*
* All literal strings and string-valued constant expressions are
* interned. String literals are defined in section 3.10.5 of the
* The Java™ Language Specification.
*
* @returns a string that has the same contents as this string, but is
* guaranteed to be from a pool of unique strings.
* @jls 3.10.5 String Literals
*/ public native String intern();
The intern()
method is used to store String
s in a String
pool. First, it verifies if the String
you’ve created already exists in the pool. If not, it creates a new String
in the pool. Behind the scenes, the logic of String
pooling is based on the Flyweight pattern.
Now, notice what happens when we use the new
keyword to force the creation of two String
s:
String duke = new String("duke");
String duke2 = new String("duke");
System.out.println(duke == duke2); // The result will be false here
System.out.println(duke.intern() == duke2.intern()); // The result will be true here
Unlike the previous example with the new
keyword, in this case the comparison turns out to be true. That’s because using the intern()
method ensures the String
s will be stored in the pool.
Using equals() with the String class
We use the equals()
method to verify whether the state of two Java classes is the same. Because equals()
is from the Object
class, every Java class inherits it. But the equals()
method must be overridden to make it work properly. Here’s how to use equals()
for a String
comparison:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String aString = (String)anObject;
if (coder() == aString.coder()) {
return isLatin1() ? StringLatin1.equals(value, aString.value)
: StringUTF16.equals(value, aString.value);
}
}
return false;
}
As you can see, the state of the String
class value has to be equals()
and not the object reference. It doesn’t matter if the object reference is different; the state of the String
will be compared.
What are the most common String methods?
You’re almost ready to take the String
comparison challenge. First, consider these common methods of the String
class:
// Removes spaces from the borders
trim()
// Gets a substring by indexes
substring(int beginIndex, int endIndex)
// Returns the characters length of the String
length()
// Replaces String, regex can be used.
replaceAll(String regex, String replacement)
// Verifies if there is a specified CharSequence in the String
contains(CharSequences)
Common mistakes with String
It can be difficult to know if two String
s are pointing to the same object, especially when the String
s contain the same value. It helps to remember that using the reserved keyword new
always results in a new object being created in memory, even if the values are the same.
Using String
methods to compare Object
references can also be tricky. The key is, if the method changes something in the String
, the object references will be different.
Let’s look at an example:
System.out.println("duke".trim() == "duke".trim());;
This comparison will be true because the trim()
method does not generate a new String
.
Now, consider this one:
System.out.println(" duke".trim() == "duke".trim());
In this case, the first trim()
method will generate a new String
because the method will execute its action, so the references will be different.
Finally, when trim()
executes its action, it creates a new String
:
// Implementation of the trim method in the String class
new String(Arrays.copyOfRange(val, index, index + len),
LATIN1);
What to remember about String
String
s are immutable, so aString
’s state can’t be changed.- To conserve memory, the JVM keeps
String
s in aString
pool. When a newString
is created, the JVM checks its value and points it to an existing object. If there is noString
with that value in the pool, then the JVM creates a newString
. - Using the
==
operator compares the object reference. Using theequals()
method compares the value of theString
. The same rule will be applied to all objects. (Also see my introduction to equals() and hashcode.) - When using the
new
operator, a newString
will be created in theString
pool even if there is aString
with the same value.
Take the String comparison challenge!
Let’s try out what you’ve learned about the String
class in a quick challenge.
For this challenge, you’ll compare a number of String
s using the concepts we’ve explored. Looking at the code below, can you determine the final value of each results variable?
public class ComparisonStringChallenge {
public static void main(String... doYourBest) {
String result = "";
result += " powerfulCode ".trim() == "powerfulCode"
? "0" : "1";
result += "flexibleCode" == "flexibleCode" ? "2" : "3";
result += new String("doYourBest")
== new String("doYourBest") ? "4" : "5";
result += new String("noBugsProject")
.equals("noBugsProject") ? "6" : "7";
result += new String("breakYourLimits").intern()
== new String("breakYourLimits").intern() ? "8" : "9";
System.out.println(result);
}
}
Which output represents the final value of the results variable?
A: 02468 B: 12469 C: 12579 D: 12568
Solving the String comparison challenge
In the first line of the code, we see:
result += " powerfulCode ".trim() == "powerfulCode"
? "0" : "1";
Although the String
will be the same after the trim()
method is invoked, the String
“ powerfulcode “
was different in the beginning. In this case the comparison is false
, because when the trim()
method removes spaces from the borders it forces the creation of a new String
with the new operator.
Next, we see:
result += "flexibleCode" == "flexibleCode" ? "2" : "3";
No mystery here, the String
s are the same in the String
pool. This comparison returns true
.
Next, we have:
result += new String("doYourBest")
== new String("doYourBest") ? "4" : "5";
Using the new
reserved keyword forces the creation of two new String
s, whether they are equal or not. In this case the comparison will be false
even if the String
values are the same.
Next is:
result += new String("noBugsProject")
.equals("noBugsProject") ? "6" : "7";
Because we’ve used the equals()
method, the value of the String
will be compared and not the object instance. In that case, it doesn’t matter if the objects are different because the value is being compared. This comparison returns true
.
Finally, we have:
result += new String("breakYourLimits").intern()
== new String("breakYourLimits").intern() ? "8" : "9";
As you’ve seen before, the intern()
method puts the String
in the String
pool. Both String
s point to the same object, so in this case the comparison is true
.
The answer to this Java challenger is Option D. The output would be 12568
.
Video challenge! Debugging String comparisons
Debugging is one of the easiest ways to fully absorb programming concepts while also improving your code. In this video you can follow along while I debug and explain the Java Strings challenge:
Learn more about Java
- Get quick code tips: Read all of Rafael’s articles in the InfoWorld Java Challengers series.
- Check out all of the videos in Rafael’s Java Challengers video playlist.
- Find even more Java Challengers on Rafael’s Java Challengers blog and in his book, with more than 70 code challenges.