Strings Are Immutable Objects
Handling “strings” of characters is a fundamental aspect of most programming languages. In Java, each character in a string is a 16-bit Unicode character. Because Unicode characters are 16 bits, a rich, international set of characters is easily represented in Unicode.
In Java, strings are objects. Just like other objects, you can create an instance of a String with the new keyword, as follows:
String s = new String();
This line of code creates a new object of class String, and assigns it to the reference variable s. So far, String objects seem just like other objects. Now, let’s give the String a value:
s = “abc”;
As you might expect, the String class has about a zillion constructors, so you can use a more efficient shortcut:
String s = new String(“abc”);
And just because you’ll use strings all the time, you can even say this:
String s = “abc”;
There are some subtle differences between these options that we’ll discuss later, but what they have in common is that they all create a new String object, with a value of “abc”, and assign it to a reference variable s. Now let’s say that you want a second reference to the String object referred to by s:
String s2 = s; //refer s2 to the same String as s
String objects seem to be behaving just like other objects, so how is it that they are Immutable? Once you have assigned a String a value, that value can never change—it’s immutable, frozen solid, won’t change. The good news is that while the String object is immutable, its reference variable is not, so to continue with our previous example:
s = s.concat(" efg");
// the concat() method 'appends' a literal to the end
Didn't I just say that Strings are immutable? Yes, I perfectly did. But, here the stuff within the double quotes that is passed as argument to the concat method gets appended to the end of the String s. How did this happen?
The VM took the value of String s (which was “abc”), and added or rather appended " efg" onto the end, giving us the value "abc efg". Since Strings are immutable, the VM couldn’t stuff this new value into the old String referenced by s, so it created a new String object, gave it the value "abc efg", and made s refer to it. At this point in our example, we have two String objects: the first one we created, with the value “abc”, and the second one with the value "abc efg". Technically there are now three String objects, because the literal argument to concat, " efg", is itself a new String object. But we have references only to “abc” (referenced by s2) and "abc efg" (referenced by s).
Note, however, that the original “abc” String didn’t change; only the reference variable s was changed, so that it would refer to a different String.
To wrap up, the original variable ‘s’ in which we had “abc” as value would be abandoned and a new value “abc efg” would get assigned to it as soon as the s.concat(“ efg”) line of code is executed.
Important Facts About Strings and Memory
One of the key goals of any good programming language is to make efficient use of memory. As applications grow, it’s very common for String literals to occupy large amounts of a program’s memory, and there is often a lot of redundancy within the universe of String literals for a program. To make Java more memory efficient, the JVM sets aside a special area of memory called the “String constant pool.” When the compiler encounters a String literal, it checks the pool to see if an identical String already exists. If a match is found, the reference to the new literal is directed to the existing String, and no new String literal object is created. (The existing String simply has an additional reference.) Now we can start to see why making String objects immutable is such a good idea. If several reference variables refer to the same String without even knowing it, it would be very bad if any of them could change the String’s value.
You might say, “Well that’s all well and good, but what if someone overrides the String class functionality; couldn’t that cause problems in the pool?” That’s one of the main reasons that the String class is marked final. Nobody can override the behaviors of any of the String methods, so you can rest assured that the String objects you are counting on to be immutable will, in fact, be immutable forever!!!.
Creating New Strings
Earlier I promised to talk more about the minor differences between the various methods of creating a String. Let’s look at a couple of examples of how a String might be created, and let’s further assume that no other String objects exist in the pool:
String s = "abc";
// creates one String object and one reference variable
In this simple case, “abc” will go in the pool and s will refer to it.
String s = new String("abc");
// creates two objects, and one reference variable
In this case, because we used the new keyword, Java will create a new String object in normal (nonpool) memory, and s will refer to it. In addition, the literal “abc” will be placed in the pool.
But, in all practical programming scenarios, either option is used interchangeably and in most cases the first option is how programmers create their String objects.
Important Methods in the String Class
The following methods are some of the more commonly used methods in the String class, and also the ones that you’re most likely to encounter on the exam. They are:
public char charAt(int index)
This method returns the character located at the String’s specified index. String indexes are zero-based—for example,
String x = "Rocky";
System.out.println( x.charAt(2) ); // output is 'c'
public String concat(String s)
This method returns a String with the value of the String passed in to the method appended to the end of the String used to invoke the method—for example,
String x = "ferrari ";
System.out.println( x.concat(" car") ); // output is "ferrari car"
The overloaded + and += operators perform functions similar to the concat() method—for example,
String x = "ferrari";
System.out.println( x + " car"); // output is "ferrari car"
String x = "ferrari";
x+= " car";
System.out.println( x ); // output is "ferrari car"
In the preceding “ferrari car” example, notice that the value of x really did change! Remember that the += operator is an assignment operator, so line 2 is really creating a new String, “ferrari car”, and assigning it to the x variable. After line 2 executes, the original String x was referring to, “ferrari”, is abandoned.
public boolean equals(String s)
This method returns a true or fals (boolean value) depending on whether the value of the two strings being compared are the same.
Note: This method checks for exact values include case sensitivity. If you just want to check the data match after ignoring case sensitivity, then you must use the equalsignorecase method explained below.
public boolean equalsignoreCase(String s)
This method returns a boolean value (true or false) depending on whether the value of the String in the argument is the same as the value of the String used to invoke the method. This method will return true even when characters in the String objects being compared have differing cases—for example,
String x = "ferrari";
System.out.println( x.equalsIgnoreCase("FERRARI")); // is "true"
System.out.println( x.equalsIgnoreCase("Bmw")); // is "false"
public int length()
This method returns the length of the String used to invoke the method—for example,
String x = "Rocky";
System.out.println( x.length() ); // returns "5"
public String replace(char old, char new)
This method returns a String whose value is that of the String used to invoke the method, updated so that any occurrence of the char in the first argument is replaced by the char in the second argument—for example,
String x = "ABABABABA";
System.out.println( x.replace('A', 'a') ); // output is
public String substring(int begin) & public String substring(int begin, int end)
The substring() method is used to return a part (or substring) of the String used to invoke the method. The first argument represents the starting location (zero-based) of the substring. If the call has only one argument, the substring returned will include the characters to the end of the original String. If the call has two arguments, the substring returned will end with the character located in the nth position of the original String where n is the second argument. Unfortunately, the ending argument is not zero-based, so if the second argument is 7, the last character in the returned String will be in the original String’s 7 position, which is index 6. Let’s look at an example:
String x = "0123456789";
System.out.println( x.substring(5) ); // output is "56789"
System.out.println( x.substring(5, 8)); // output is "567"
The first example should be easy: start at index 5 and return the rest of the String. The second example should be read as follows: start at index 5 and return the characters up to and including the 8th position (index 7).
Exam Tip: Arrays have an attribute (not a method), called length. You may encounter questions in the exam that attempt to use the length() method on an array, or that attempt to use the length attribute on a String. Both cause compiler errors—for example,
String x = "test";
System.out.println( x.length ); // compiler error
String x = new String;
System.out.println( x.length() ); // compiler error
public String toLowerCase()
This method returns a String whose value is the String used to invoke the method, but with any uppercase characters converted to lowercase—for example,
String x = "ROCKY";
System.out.println( x.toLowerCase() ); // output is “rocky”
public String toString()
This method returns the value of the String used to invoke the method. You are probably thinking why do I need a toString() method for a String? Isnt it useless or redundant? To answer you my friend, if you remember String is an Object and all java objects have a toString() method. And hence, eventhough the current object under consideration is a String, we still have a toString() method. For example:
String x = "Rocky";
System.out.println( x.toString() ); // output – “Rocky”
public String toUpperCase()
This method returns a String whose value is the String used to invoke the method, but with any lowercase characters converted to uppercase—for example,
String x = "rocky";
System.out.println( x.toUpperCase() ); // output is “ROCKY”
public String trim()
This method returns a String whose value is the String used to invoke the method, but with any leading or trailing blank spaces removed—for example,
String x = " Rock ";
System.out.println( x + "y" ); // result is " Rock y"
System.out.println( x.trim() + "y"); // result is "Rocky"
Previous Chapter: Self Test - Chapters 27 to 31
Next Chapter: Chapter 33 - StringBuffer and StringBuilders