2-4 Working with Objects: Strings

So far we have learned how to define String objects, assign them values, and concatenate (+) them, but there is much more we can do with strings! We’ll start with three important facts to know about strings, and then cover many of the rich set of String methods in Java.

Fact #1: Strings are Objects

In 1-2 we learned that strings are objects and not a primitive data type because they include methods, such as equals(). You’ll recall that when we have created other objects (like Scanner) we’ve needed to use the new keyword to instantiate (create) the object:

Yet when we’ve created String objects the new keyword has not been necessary:

Why is this? The reason is that since strings are so commonly used in Java the line above is accepted shorthand for instantiating a String object. The Java compiler actually interprets the line above as if you had typed something like:

Proof that strings really are objects! Both ways of declaring a String work but I think you’ll agree that the shorthand way is more convenient. This shorthand only works for String objects.

Fact #2: Strings are Immutable

The next important thing to understand about String objects is that they are immutable. Immutable means that once a string object has been assigned a string value, that value may not be changed. Consider the following example:

As you would expect, the output of this program would be:

Hello, I hear you are 16 years old!
Hello James, I hear you are 17 years old!

On the surface it appears that String and int variables are modified in the same way, but it’s critical to understand what is actually happening behind the scenes. After line 6 executes, the age and message variables look like this in memory:

2-4-Strings1After line 10 executes, this is what happens to them:

2-4-Strings2You can see that when the primitive int variable age is modified on line 10, the memory location that contained its initial value (16) is simply overwritten (recycled) with the new value (17). For the String object message it works differently. Rather than overwriting the String “Hello” in memory, a brand new String object “Hello James” is created and the message reference variable then points to this new String in memory. Because the old String object “Hello” no longer has any references (arrows) pointing to it, the JVM will remove it from memory by an automatic process called garbage collection. So, any time we “change” a String, we are actually creating a new String in memory. Keep this in mind as we explore the methods of the String class.

Fact #3: Strings are Indexed

The final important concept to understand about strings is that each character in a String has an index number. For example, you can think of the string “Hello James” being stored in memory like this:

2-4-Strings3The index of the first character always starts at 0. Indexes are important when we want to access specific characters or substrings within a string.

Although each character in a string has an index, they are not easily indexable like other languages such as Python and C++.  In other words, one cannot access a character directly using an index like so:

Instead, there are String methods such as  charAt() and substring() to allow us to access parts of a string using indexes.

With these three facts understood, let’s look at some useful String methods.

Basic String Methods: length and charAt()

The length() method returns the total number of characters in a String object. Note that because strings are indexed from 0, this is not the same as the index of the last character in the string. For example, the length of the String “Hello James” is 11 but the index of the last character is actually 10.

The charAt() method returns the char (primitive type!) at a particular index.

s1: “Hello There!”
s2: “Vik-20!”
Length of s1: 12
s2 reversed is: !02-kiV
s1: “Hello There!”
s2: “Vik-20!”

Comparing Strings: equals(), equalsIgnoreCase(), and compareTo()

You know that we can’t use any of the relational operators (==, >, >=, <, <=, or !=) to compare string objects because these would compare the memory locations of the strings and not their actual contents. To get around this, we learned about the equals() method in 1-6.

The equals() method returns a true or false value depending on whether all of the characters in the current String object are exactly the same (including case) as the characters in a parameter String object. To check for equality, but ignore differences in upper or lower case, use the equalsIgnoreCase() method instead.

To compare two String objects to determine their alphabetical order you can use the compareTo() method. The compareTo() method returns one of three (integer) possibilities:

2-4-CompareMethodsHere’s an example to demonstrate:

s1 = Hello
s2 = hello
s1 does not equal “hello”
Hello equals hello with case ignored
s1.compareTo( s1 ) gives 0
s1.compareTo( s2 ) gives -2
s2.compareTo( s1 ) gives 2

You’ll notice in the last two lines of output the compareTo() method gives -2 or +2. The 2 represents the difference in Unicode character value between the “H” in “Hello” (s1) and the “J” in “Jello” (s2).

Indexing Strings: indexOf(), lastIndexOf(), and substring()

The indexOf() and lastIndexOf() methods allow us to find the index of a given char or substring within the current String object. If the char or substring cannot be found, -1 is returned. There are four overloaded versions of these methods:

2-4-IndexMethods1There are also four similar methods that search from the end of the string toward the front:

2-4-IndexMethods2Finally, the substring() method returns a reference to a new String object made up of a span of characters copied from the current String object. There are two overloaded versions of this method:

2-4-IndexMethods3Here’s an example to demonstrate these methods:

– – – – – 11111
012345678901234
123abc$ 123abc$
‘a’ is at index 3
‘x’ is at index -1
‘2’ is at index 1
‘a’ is at index 11
‘x’ is at index -1
‘2’ is at index 1
“abc” starts at index 3
“xyz” starts at index -1
“abc” starts at index 11
“abc” starts at index 11
“xyz” starts at index -1
“abc” starts at index 3
Substring from index 10 to end is “3abc$”
Substring from index 3 up to, but not including 6 is “abc”

Modifying Strings: replaceAll(), toUpperCase(), toLowerCase(), trim()

Since String objects are immutable, one cannot actually modify a String. But there are some methods that will return a reference to a new copy of a String object with changes applied.
2-4-ModifyMethodsHere is a demonstration of these methods in action:

Replace “l” with “ha-” in s1: Hehahao, do YOU Love Java?
s1.toUpperCase() = HELLO, DO YOU LOVE JAVA?
s1.toLowerCase() = hello, do you love java?
s2 before trim = ”               tabs and spaces
are        gone

s2 after trim = “tabs and spaces
are        gone”
s1 = Hello, do YOU Love Java?
s2 = ”      tabs and spaces
are        gone

s1 = HELLO, DO YOU LOVE JAVA?
s2 = “tabs and spaces
are        gone”

Again, none of these methods actually changes the original String object, they are simply returning a reference to a copy of a new String object with changes applied (which we are immediately printing). To actually change a String, you need to assign its reference variable to the new copy as shown on lines 17 and 18.

Creating a Formatted String: format()

The String class includes a special method called format() that takes the same parameters as printf() but instead of displaying a formatted string it returns a reference to a new String object with the desired formatting applied:

Notice that I have not used the new keyword anywhere in this example to instantiate a String object. On line 5, the variable outputString is declared as a reference variable to a String object but this does not actually create a String object.

You can see the format() method in action on line 7. Unlike the other String class methods, the format() method is called using the String class name followed by .format(). The format() method itself instantiates a String object, applies the desired formatting to it, and returns a reference to the new String object which we assign to our outputString reference variable. At this point nothing has been displayed on the screen.

On line 8 we show the formatted outputString in a GUI dialog box. This is a particularly good example because the showMessageDialog() only accepts two parameters and does not support format specifiers directly. Without the ability to create a formatted String object this would not be possible.

NullPointerException Errors

Before we end, I want to mention a common exception that can occur when working with objects. Consider the following code:

The first line is declaring a reference variable for a String object, but it does not actually point to a String object in memory yet. Because of this if we attempt to call the toUpperCase() method the program will crash with a NullPointerException. The point is that you need to be sure that a reference variable actually refers to an object in memory before attempting to call its methods.

You Try!

  1. Use the String.format() method to neatly round the final result of your GUI compound interest calculator (from 1-5) to 2 decimal places.
  2. The Character class has many useful methods for testing various char properties.  These methods work like the methods of the Math class: they are part of the java.lang package so you don’t need to import the class, and you do not need it instantiate a Character object to use the methods, just use the class name Character. followed by the method you’d like to call. Write a program to demonstrate the following methods: isDigit(), isLetter(), isLetterOrDigit(), isLowerCase(), isUpperCase(), toUpperCase(), and toLowerCase().
  3. Let’s make our own methods that operate on strings. Complete all of the methods below including the main() method to demonstrate their use.
  4. Create a Cities application that prompts the user for a series of city names and then displays the number of city names entered and then the name of the city (in UPPERCASE) with the most characters of those entered.  The application output should look similar to the following:

    Enter a city name (or ‘done’ to stop): Toronto
    Enter a city name (or ‘done’ to stop): Vancouver
    Enter a city name (or ‘done’ to stop): Montreal
    Enter a city name (or ‘done’ to stop): done
    3 names were entered, the longest was VANCOUVER!