15Nov 2021

All about Java Strings

String is one of those data types that are universally easy to use, no matter what programming language. It’s the data type you use to build “hello world” programs. Remember this?

public class StringSample {
  public static void main(String[] args) {
    System.out.println("Hello World");
  }
}

Whenever you enclose a series of alphanumeric characters within double quotes, that’s a String in Java. Double quotes, mind you. Not single quotes — this isn’t Python.

Java Strings are reference types, not primitive types. Strings are objects, and that’s what makes them very useful (and interesting). 

hello-world

In this article, we’ll explore Strings a bit more in detail. We’ll poke into its internal implementations. 

Creating Strings

The simplest way to create a String is something like this;

String name = “Hello World”;

Just enclose a bunch of letters (and or numbers) within double quotes, and you should be done.

If you’re using Java 10 or above (and you should be, it’s 2021 already), you don’t even have to declare it as a String. You can declare it like this;

var str = “Hello World”;

The Java compiler is smart enough (now) to figure out the type to use because of the literal on the right-hand side.

Using String literals may be the most common way of creating Strings, but there are other ways.

String name = new String();

The code above creates a new String object whose length is zero. If you want to find out for yourself, you can test the following code (either in a proper source file or in a Jshell)

String s = new String();
System.out.printf(“length of String is %d”, s.length());

// prints “length of String is 0”

The easier way to create a zero-length String is, of course, like this

String s = “”;

Another way to create a String is to pass a String literal to its constructor, like this

String name = new String(“James Gosling”);

You can create a String object by passing a character array into its constructor, like this

char nameArray[] = {‘J’,’a’,’m’,’e’,’s’,’ ‘,’G’,’o’,’s’, ‘l’,’i’,’n’,’g’};
String name = new String(nameArray);
System.out.printf(“nameArray reads %s”, name);

Of course, you’re not going to write codes like the sample above. That’s ridiculous. It’s a made-up example.

But you may have to work with Strings that are given as byte arrays (like the one above) when you work with Java I/O or network APIs. Then, the above examples will become useful.

Immutable Strings

immutable-strings

Immutable means “not changeable”. You cannot mutate it. What it means for String is that once you create a String, you can no longer change it. Really? Then how come we can do these things;

String str = "Hello";
str = "World";


Didn’t I just change the contents of str there? Surely, if I can change the value of the str variable, then I’m able to modify the String value as held by the str variable? Right? Well, no. Not really. 

You see, this is what happens when you create a String object.

create-string-object

Somewhere in the heap (a region in the memory), Java creates the actual String object “Hello”; and the variable str, points to it. 

When you assign a new value to the str variable, like this;

str = “World”;

Java will not edit the original “Hello” String object and change it in place. Instead, it will create another String object somewhere in the heap and point the str variable to the newly created String object (“World”) — which effectively orphans the original String object (“Hello”). 

string-hello-world

Orphaned objects — objects that are no longer referenced by any variable — become candidates for garbage collection.

Let’s take another example;

String empName = "James";  // (1)
empName += " ";            // (2)
empName += "Gosling";      // (3)

(1) Created a new String from the literal “James”

(2) Copied the contents of the empName variable (currently contains “James”), then appended a space and created a second String object, contents of which is “James ” (with empty space); then this new object was assigned to empName, now the empName variable points to “James “ (length = 5) and no longer to “James” (length = 4)

(3) At this point, we take the content of empName (“James “, length = 5) and append “Gosling”. Same thing as number 2 above, the runtime copies “James “, adds “Gosling” and re-assigns it to variable empName. Now, empName points to “James Gosling”. The previous String objects “James” and “James “ are now orphan objects, which makes them a candidate for garbage collection.

This is a very common operation. Accumulating String by using the addition operator is a very common technique among developers, and there’s nothing wrong with this concatenation if you won’t be creating too many objects.

Be extra careful with String concatenation when you’re inside loops because things can go very wrong (and very fast) inside loops.

Statement stmt=con.createStatement();
ResultSet rs=stmt.executeQuery("select * from tblEmployees");

String result = "";
while(rs.next()){
  result += rs.getString(1) + " " + rs.getString(2);
}
System.out.println(result);
con.close();

If the table has tens of thousands of rows, this code will be a source of performance problems. It can drag your app to a screeching halt as you run it. 

Remember that Strings are immutable, and every time you do concatenation, Java will create a new String object.

If the table has 10 thousand rows, the code will easily create 20 thousand String objects by the time the while loop has exited.

There will be 20K orphaned objects waiting for the garbage collector. The garbage collector’s schedule cannot be predicted, and by the time the GC sweeps the heap, there might not be enough memory left for your app. This can slow down the app’s performance.

If you really need to concatenate Strings inside loops, it will be better to use either the StringBuilder or the StringBuffer class. These classes are mutable, unlike the String class. Let’s rewrite our database code using the StringBuffer.

String result = "";
StringBuffer sb = new StringBuffer();
while(rs.next()){
  sb.append(rs.getString(1));
  sb.append(" ");
  sb.append(rs.getString(2));
}
result = new String(sb);
System.out.println(result);
con.close();

The append() method of StringBuffer lets you add new String objects to the buffer, but unlike the String object, the StringBuffer does not create another StringBuffer object.

It modifies its internal structure to accommodate the new content. Later on, you can convert the StringBuffer to a String object by passing the StringBuffer to the String’s constructor, like this

result = new String(sb);

Why Strings are immutable

why-strings-are-immutable

If you peek at the source code of java.lang.String, you’ll find the following declarations

public final class String

    implements java.io.Serializable, Comparable<String>, CharSequence,

               Constable, ConstantDesc {

    private final char[] value;

    //… other statements

}

It implements a couple of interfaces, which are interesting, but not part of our current discussion, so we won’t get distracted by that. Notice that the class is final, which means you cannot subclass or extend it.

What’s more important to notice is the member variable named value. It’s an array, and it’s declared as final. This array is used to store the characters of the String value. Arrays are fixed.

Once you create them, you can no longer add any more elements. Arrays cannot grow in size after they have been defined. This is the main reason why you cannot modify the contents of a String without creating a copy of the original.

String comparison

Strings are reference types, which means they are stored on the heap; so, when comparing them with one another, you generally cannot use the equality operator ==. To compare strings, you should use the .equals() method, like this 

String str1 = "Hello";
String str2 = "Hello";

if(str1.equals(str2)) {
  System.out.println("str1 is equal to str2");
}

However, you may see some programmers use the == to compare Strings like this

String string1 = "Hello";
String string2 = "Hello";

if(string1 == string2) {
  System.out.println("string1 is equal to string2");
}

And it actually works. So, what’s going on? This is a special case for the String object. The rule you should follow when comparing Strings for lexical equivalence is to always use the .equals() method. 

The == operator is what you use for comparing primitive values, not object types, but why is it working in previous code examples?

The answer is because of the String constant pool (other programmers call it String literal pool or simply String pool, these terms may be used interchangeably).

The String constant pool is a special area in heap memory that the runtime uses to create String literals, and once a String literal is created in this area, it can be reused so as to avoid duplicate copies of the same literal.

Let’ see that in code.

String str1 = "Good morning";  // (1)
String str2 = "Good morning";  // (2)

if (str1 == str2) {
  System.out.println("str1 == str2");
}

(1) The runtime creates a new String object and places it in the String constant pool

(2) When str2 is declared and defined, the runtime will search the String constant pool if this literal has been created already; and it is, so, Java won’t create another “Good morning” String object; instead, the address of the existing “Good morning” String will be assigned to the str2 variable. Now, str1 and str2 point to exactly the same address. This is why when you test them using double equals, the expression returns true.

An important point to remember about the String constant pool is that String literals end up in the String constant pool only when String literals are plainly assigned to a variable, like this

String str2 = "Good morning";  

When you create a String object using constructors (with the new keyword), the String objects don’t end up in the constant pool. Let’s consider the following code.

String str1 = "Good morning";             // (1)
String str2 = new String("Good morning"); // (2)

if (str1 == str2) {                       // (3)
  System.out.println("str1 == str2");
}

(1) This gets stored on the String constant pool, str1 points to “Good morning” in the constant pool

(2) This gets stored on the heap like all other objects, str2 points to “Good morning” in the regular heap, not in the constant pool

(3) str1 contains the address of an object in the constant pool; str2 contains the address of a completely different object in heap memory. This happened because we used the new keyword in creating the String object. Whenever you use the new keyword, it creates a new object in the heap. 

If you want to compare Strings without care or regard for case sensitivity, you can use the .equalsIgnoreCase() method, like this

String str1 = "Hello";
String str2 = "hello";

if(str1.equalsIgnoreCase(str2)) {
  System.out.println("str1 is equal to str2");
}

Common usage of Strings

common-usage-of-strings

indexOf(String arg)

The indexOf() method takes either a character or String arguments and searches the String for any match. When a match is found, the ordinal position of the argument in the String is returned; for example

String letters = “ABCAB”; 

System.out.println(letters.indexOf(‘B’));   // prints 1

System.out.println(letters.indexOf(“S”));   // prints -1

System.out.println(letters.indexOf(“CA”));  // prints 2

When a match is not found, -1 is returned

substring(int position)

The substring() method takes an integer argument which it uses to mark the beginning position from where to extract the substring.

If you don’t pass a second argument to this method, then it will return all the strings from the beginning position (marked by the argument) up to the last character of the String; for example

String exam = “Oracle”;
String sub = exam.substring(1); 

System.out.println(sub);          // racle

You can also specify the ending position of the substring to extract by passing the second integer argument, like this

String exam = “Oracle”;
String result = exam.substring(2, 4); 

System.out.println(result);      // prints ac

charAt(int indexPosition)

You can use this method to retrieve a character at a specified index of the String. Take note that the ordinal positions of characters in a String start at zero, not one.

Remember that a char array is used within the internal mechanism of the String class, and Java arrays are zero-based. Let’s look at an example

String name = new String(“James”); 

System.out.println(name.charAt(0));  // prints J 

System.out.println(name.charAt(2));  // prints m

trim()

The trim() method returns a new String object, but all the whitespaces (leading or trailing) are removed. 

String withSpaces = ” AC DC “;

String trimmed = withSpaces.trim(); // returns “ACDC” 

Note that the space between AB and CB has not been trimmed. The trim function only removes leading and trailing white spaces. It doesn’t’ remove the ones in between.

length()

This method returns the number of characters in a String. Please don’t confuse the String’s length() method with the array’s length property

String withSpaces = ” AB CB “;
System.out.printf(“Length of %s is %d”, withSpaces, withSpaces.length()); // prints 7

startsWith() and endsWith()

Both these methods take an argument and it matches those arguments against the series of characters in the String. In the case of startsWith(), if the argument passed to is a match to the characters in the String beginning at position 0, then the method returns true. See this example

String letters = “ABCAB”; 

boolean a  = letters.startsWith(“AB”)); // true 

boolean b = letters.startsWith(“a”));   // false 

boolean c = letters.endsWith(“CAB”)     // true

boolean d = letters.endsWith(“B”)       // true

boolean e = letters.endsWith(“b”)       // false

replace()

This method takes two arguments;

  • 1st argument is the pattern to match. The method tries to find all the occurrences of this pattern within the String
  • 2nd argument is the replacement String; if the method finds a match for the first argument, all matching string will be replaced by the 2nd argument. Think of it as find-and-replace functionality in your editor

String letters = “ABCAB”;
String result letters.replace(‘B’, ‘b’); // returns AbCAb 

You can also use Strings as argument, like this

String letters = “ABCAB”;

String result = letters.replace(“CA”, “12”); // returns AB12B

This is an article under Acodes – coding article series from Acodez. You can read more coding articles here: ACodes Series

Acodez is a renowned web development company and web application development company in India. We offer all kinds of web design and Mobile app development services to our clients using the latest technologies. We are also a leading digital marketing company providing SEO, SMM, SEM, Inbound marketing services, etc at affordable prices. For further information, please contact us.

 

Looking for a good team
for your next project?

Contact us and we'll give you a preliminary free consultation
on the web & mobile strategy that'd suit your needs best.

Contact Us Now!
Jamsheer K

Jamsheer K

Jamsheer K, is the Tech Lead at Acodez. With his rich and hands-on experience in various technologies, his writing normally comes from his research and experience in mobile & web application development niche.

Get a free quote!

Brief us your requirements & let's connect

Leave a Comment

Your email address will not be published. Required fields are marked *