Introduction

String interning is a concept that not many Developers out there know. It’s deeply bounded to the way the JVM handles memory.

Nowadays it’s easier than ever to leverage the existence of built-in functions, libraries, frameworks… Don’t get me wrong, they help us stop reinventing the wheel over and over.

However, I also feel like it’s hard to know what’s going on underneath. This may not even be a problem at all unless you have to deal with specific circumstances, such as memory management.

Therefore, it’s quite useful to, at least have, a basic understanding of everything you can. By digging deeper, eventually, not everything will be a black box to you.

In today’s post, we will talk about how Strings work in Java, how the JVM handle them, the best way to treat them, and some useful information. I hope you like it. 😊

How does the JVM handle Strings

String is a special kind of class in Java. It’s the only one that we can instantiate with double quotes. The other classes that can be instantiated without using the new keywords are the primitive types.

What happens when you instantiate a String with double quotes?

Java has what is known as String pool. We can think of it as a bag that contains Strings. Every time we create a String that is not yet in the String pool, the JVM adds it.

As we can see in the example, once the String a is created, “Hello” is added to the String pool. Then a new String b is referenced to a. Remember that using the = operator in Java means that the left side (in this case the String b) will point to the memory address of the right side (the String a). However, in the case of the Strings, b is now pointing to the “Hello” String in the String pool.

When the String c is created, given that is initialized to “World” and it is not yet in the String pool, it gets added there as well.

This way, the JVM optimizes memory allocation and consumption as it will only allocate the space of the “Hello” String once.

A curious thing is that you can use the new operator to create a String as shown in the example. When we create the String d using this new operator, instead of it pointing to the already existing “Hello” String in the pool, it allocates memory for it as it would do for a regular object.

This is why you should not create Strings using the constructor.

Immutability

A really important concept about the String class is that it’s immutable. Now, what does this mean? An immutable object is one that can’t be modified. Therefore, when we want to modify an immutable object, we have to create another one. Once we instantiate an immutable object, we won’t be able to change its value.

This happens with many other classes in Java, such as Date, all the wrapper classes of the primitives’ types: Integer, Double and so many more.

This also means that any operation performed over a String won’t modify the String. It will instead, create a new one. That’s why, when you perform an operation on a String but don’t assign it back, nothing will change in the original String.

String test = "Hello";
test.concat(" World");
System.out.println(test); // "Hello"

test = test.concat(" World");
System.out.println(test); // "Hello World"

What happens under the scenes here is that when the String test is reassigned to the output of test.concat(" World"); a new String is created: "Hello World". The JVM then adds this String This new String to the String pool (if not present yet) and then test will point to this new String in the pool.

Equals vs == operator

These previous explanations come in handy when we think about how should we check that a String is the same as another one.

We all know that the == operator returns true when the memory address of the two objects compared is the same. So, for instance:

int num1 = 5;
int num2 = num1;
System.out.println(num1  == num2); // true

num1 is initialized to 5, the JVM allocates memory for this integer 5 and then makes num1 point to that memory address. When num1 is assigned to num2, both are pointing to the same memory address which means that the == operator will return true.

What happens with Strings then?

String str1 = "Hello";
String str2 = "Hello";
System.out.println(str1 == str2); // true

String str3 = new String("Hello");
String str4 = new String("Hello");
System.out.println(str3 == str4); // false
System.out.println(str1 == str3); // false

String str5 = str3.intern();
System.out.println(str1 == str5); // true

There are a couple of things to explain here.

  • str1 == str2 -> true. As we explained before, both are pointing to the same memory address.
  • str3 == str4 -> false. Due to str3 and str4 are both instantiated with the String constructor, both are pointing to a different memory address.
  • str1 == str3 -> false. One string str1 is in the String pool, the other String str3 is not.
  • str1 == str5 -> true. Quickly explained, the intern() method interns the given String, that is to say, performs String internment in the String pool. Therefore, both are pointing to the same String.

So what do we do? Do we just spin a wheel and accept our fate? Well, actually there’s a better approach, use equals to compare Strings.

The equals method allows us to compare the content of the Strings, rather than the memory address. As a result, the previous example with equals would be:

String str1 = "Hello";
String str2 = "Hello";
System.out.println(str1.equals(str2)); // true

String str3 = new String("Hello");
String str4 = new String("Hello");
System.out.println(str3.equals(str4)); // true
System.out.println(str1.equals(str3)); // true

String str5 = str3.intern();
System.out.println(str1.equals(str5)); // true

No matter what we do, all Strings here have the same content, therefore, the equals method returns true for all of them.

Conclusion

I think I gave you enough reasons to remember that you should always use the equals method to compare Strings and try to avoid the == operator.

Soon enough, I will write a blog post on how equals internally work but until then, feel free to investigate and play around on your own (as the best Developers do). Here are some references to get started though. Such as a guide to the Java String pool or some more examples on String interning.

Hope you found this post useful and enjoyed reading it. If you did, you will find my socials at the bottom of this page. You know what to do next 😉 (much appreciated).

Otherwise, or if you feel like you want to give me your insights on this topic, don’t hesitate to post a comment. I’ll be so happy to help/read your suggestions.