Skip to main content
Logo image

Java, Java, Java: Object-Oriented Problem Solving, 2024E

Section 7.5 From the Java Library: java.lang.StringBuffer

One problem with the keywordSearch() method (Listing 7.4.2) is that it is not very efficient because a String in Java is a read-only object. This means that once it has been instantiated, a String cannot be changed. You cannot insert new characters or delete existing characters from it.
Given this fact, how is it possible that the resultStr in the keywordSearch() ends up with the correct value? The answer is that every time we assign a new value to resultStr, Java has to create a new String object. Figure 7.5.2 illustrates the process. Thus, given the statement
resultStr = resultStr + ptr + " ";
Java will evaluate the right-hand side, which creates a new String object whose value would be the concatenation of the right-hand-side elements, resultStr + ptr + " " (Part (a) of Figure 7.5.2). It would then assign the new object as the new referent of resultStr (Part (b) of Figure 7.5.2). This turns the previous referent of resultStr into an orphan object —that is, into an object that no longer has any references to it.
Figure 7.5.2. Evaluating resultStr = resultStr + ptr + " " creates an orphan object that must be garbage collected.
Java will eventually dispose of these orphaned objects, removing them from memory in a process known as garbage collection. However, creating and disposing of objects is a task that consumes the computer’s time.
The fact that this assignment statement occurs within a loop means that several new objects are created and later garbage collected. Because object creation is a relatively time-consuming and memory-consuming operation, this algorithm is somewhat wasteful of Java’s resources.
Of course, except for the inefficiency of doing it this way, no real harm is done by this algorithm used in the keywordSearch() method. Java’s garbage collector will automatically reclaim the memory used by the orphaned object. However, this algorithm does consume more of Java’s resources than other algorithms we might use.
A more efficient way to write the keywordSearch() method would make use of a StringBuffer (Figure 7.5.4) to store and construct the resultStr. Like the String class, the java.lang.StringBuffer class also represents a string of characters. However, unlike the String class, a StringBuffer can be modified, and it can grow and shrink in length as necessary.
Figure 7.5.4. The java.lang.StringBuffer class.
The StringBuffer class contains several of the same kind of methods as the String class, for example, charAt() and length(). But it also contains methods that allow characters and other types of data to be inserted into a string, such as append(), insert(), and setCharAt(). Most string-processing algorithms use a StringBuffer instead of String as their preferred data structure.
The StringBuffer class provides several methods that are useful for string processing. The constructor method, StringBuffer(String), makes it easy to convert a String into a StringBuffer. Similarly, once you are done processing the buffer, the toString() method makes it easy to convert a StringBuffer back into a String.
The typical way to use a StringBuffer is shown in the following revised version of the keywordSearch() method:
public String keywordSearch(String s, String keyword) 
{
                                   // Create StringBuffer
  StringBuffer resultStr = new StringBuffer();
  int count = 0;
  int ptr = s.indexOf(keyword);
  while (ptr != -1) {
    ++count;
    resultStr.append(ptr + " ");  // Append to buffer
    ptr = s.indexOf(keyword, ptr + 1);
  }
  resultStr.insert(0, count + ": ");
  return resultStr.toString(); // Convert buffer to String
} // keywordSearch()
Listing 7.5.6. The revised keywordSearch() mrthod.
We declare resultStr as a StringBuffer instead of a String. Then, instead of concatenating the ptr and reassigning the resultStr, we append() the ptr to the resultStr for each occurrence of a keyword. Similarly, after the loop exits, we insert() the count at the front (index 0) of the resultStr. Finally, we convert resultStr into a String by using the toString() method before returning the method’s result.
One advantage of the StringBuffer class is that there are several versions of its insert() and append() methods. These make it possible to insert any type of data —int, double, Object, and so on —into a StringBuffer. The method itself takes care of converting the data into a string for us.
To summarize, String objects in Java are immutable. So when a String is “modified,” this really means that a new String object is created and the old String object must be garbage collected. This is somewhat inefficient, especially if done repeatedly within a loop. To avoid these inefficiencies, use a StringBuffer instead of a String in such contexts.

Exercises Self-Study Exercise

1. Revised Keyword Search.

Run the code below to test the revised keywordSearch() method. Add additional calls to keywordSearch().
You have attempted of activities on this page.