String objects cannot be changed, but we have been creating strings that are combinations and modifications of existing String objects, so how is this done? Java has another standard class for defining strings, StringBuffer, and a StringBuffer object can be altered directly. Strings that can be changed are often referred to as mutable strings whereas a String object is an immutable string. Java uses objects of the class StringBuffer internally to perform many of the operations on String objects. You can use a StringBuffer object whenever you need a string that you can change directly.
So when do you use StringBuffer objects rather than String objects? StringBuffer objects come into their own when you are transforming strings - adding, deleting, or replacing substrings in a string. Operations will be faster and easier using StringBuffer objects. If you have static strings, which you occasionally need to concatenate, then String objects will be the best choice. Of course, if you want to you can mix the use of both in the same program.
You can create a StringBuffer object that contains a given string with the statement:
StringBuffer aString = new StringBuffer("A stitch in time");
This declares a StringBuffer object, aString, and initializes it with the string A stitch in time. When you are initializing a StringBuffer object, you must use this syntax, with the keyword new, the StringBuffer class name, and the initializing value between parentheses. You cannot just use the string as the initializing value as we did with String objects. This is because there is rather more to a StringBuffer object than just the string that it contains initially, and, of course, a string literal is a String object by definition.
You can just create the StringBuffer variable, in much the same way as you created a String variable:
StringBuffer myString = null;
This variable does not refer to anything until you initialize it with a defined StringBuffer object. For example, you could write:
myString = new StringBuffer("Many a mickle makes a muckle");
which will initialize it with the string specified. You can also initialize a StringBuffer variable with an existing StringBuffer object:
myString = aString;
Both myString and aString will now refer to a single StringBuffer object.
The String objects that we have been using each contain a fixed string, and memory is allocated to accommodate however many Unicode characters are in the string. Everything is fixed so memory usage is not a problem. A StringBuffer object is a little different. It contains a block of memory called a buffer, which may or may not contain a String, and if it does, the string need not occupy all of the buffer. Thus the length of a string in a string object can be different from the length of the buffer. The length of the buffer is referred to as the capacity of the StringBuffer object.
StringBuffer aString = new StringBuffer("A stitch in time"); int theLength = aString.length();
If the object aString was defined as in the declaration above, the variable theLength will have the value 16. However, the capacity of the object is larger, as illustrated in the diagram.
When you create a StringBuffer object from an existing string, the capacity will be the length of the string plus 16. Both the capacity and the length are in units of Unicode characters, so twice as many bytes will be occupied in memory.
The capacity of a StringBuffer object is not fixed though. For instance, you can create a StringBuffer object with a given capacity by specifying the capacity when you declare it:
StringBuffer newString = new StringBuffer(50);
This will create an object, newString, with the capacity to store 50 characters. If you omitted the capacity value in this declaration, the object would have a default capacity of 16 characters.
A String object is always a fixed string, so capacity is irrelevant - it is always just enough to hold the characters in the string. A StringBuffer object is a container in which you can store any string and therefore has a capacity - a potential for storing strings up to a given size. Although you can set it, the capacity is unimportant in the sense that it is just a measure of how much memory is available to store Unicode characters at this particular point in time. You can get by without worrying about the capacity of a StringBuffer object since the capacity required to cope with what your program is doing will always be provided automatically. It just gets increased as necessary.
On the other hand, the capacity of a StringBuffer object is important in the sense that it affects the amount of overhead involved in storing and modifying a string. If the initial capacity is small, and you store a string that is long, or you add to an existing string significantly, extra memory will need to be allocated, which will take time. It is more efficient to make the capacity of a StringBuffer sufficient for the needs of your program.
To find out what the capacity of a StringBuffer object is at any given time, you use the capacity() method for the object:
int theCapacity = aString.capacity();
This method will return the number of Unicode characters the object can currently hold. For aString defined as shown, this will be 32. When you create a StringBuffer object containing a string, its capacity will be 16 characters greater than the minimum necessary to hold the string.
The ensureCapacity() method enables you to change the default capacity of a StringBuffer object. You specify the minimum capacity you need as the argument to the method, for example:
If the current capacity of the aString object is less than 40, this will increase the capacity of aString by allocating a new larger buffer, but not necessarily with a capacity of 40. The capacity will be the larger of either the value you specify, 40 in this case, or twice the current capacity plus 2, which is 66, given that aString is defined as before.
You can change the length of the string contained in a StringBuffer object with the method setLength(). Note that the length is a property of the string the object holds, as opposed to the capacity, which is a property of the string buffer. When you increase the length for a StringBuffer object, the extra characters will contain '\u0000'. A more common use of this method would be to decrease the length, in which case the string will be truncated. If aString contains "A stitch in time", the statement:
will result in aString containing the string "A stitch", and the value returned by the length() method will be 8. The characters that were cut from the end of the string by this operation are lost.
To increase the length to what it was before, you could write:
Now aString will contain:
The setLength() method does not affect the capacity of the object unless you set the length to be greater than the capacity. In this case the capacity will be increased to accommodate the new string length to a value that is twice the original capacity plus two if the length you set is less than this value. If you specify a length that is greater than twice the original capacity plus two, the new capacity will be the same as the length you set. If the capacity of aString is 66, executing the statement.
will set the capacity of the object, aString, to 134. If you supplied a value for the length of 150, then the new capacity would be 150. You must not specify a negative length here. If you do a StringIndexOutOfBoundsException exception will be thrown.
The append()method enables you to add a string to the end of the existing string stored in a StringBuffer object. This method comes in quite a few flavors, but perhaps the simplest adds a String constant to a StringBuffer object.
If we have defined a StringBuffer object with the statement:
StringBuffer aString = new StringBuffer("A stitch in time");
we can add to it with the statement:
aString.append(" saves nine");
after which aString will contain "A stitch in time saves nine". The length of the string contained in the StringBuffer object will be increased by the length of the string that you add. You don't need to worry about running out of space though. If necessary, the capacity will be increased automatically to accommodate the longer string.
The append() method returns the extended StringBuffer object, so you could also assign it to another StringBuffer object. Instead of the previous statement, you could have written:
StringBuffer bString = aString.append(" saves nine");
Now both aString and bString point to the same StringBuffer object.
If you take a look at the operator precedence table back in Chapter 2, you will see that the '.' operator (sometimes called the member selection operator) that we use to execute a particular method for an object has left-to-right associativity. You could therefore write:
StringBuffer proverb = new StringBuffer(); // Capacity is 16 proverb.append("Many").append(" hands").append(" make"). append(" light").append(" work.");
The second statement is executed from left to right, so that the string contained in the object proverb is progressively extended until it contains the complete string.
Another version of the append() method will add part of a String object to a StringBuffer object. This version of append()requires you to specify two additional arguments: the index position of the first character to be appended, and the total number of characters to be appended. This operation is shown in the following diagram.
This operation appends a substring of aString consisting of four characters starting at index position 3 to the StringBuffer object, buf. The capacity of buf is automatically increased by the length of the appended substring, if necessary.
You have a set of versions of the append() method that will enable you to append() any of the basic types to a StringBuffer object. These will accept arguments of any of the following types: boolean, char, byte, short, int, long, float, or double. In each case, the value is converted to a string equivalent of the value, which is appended to the object, so a boolean variable will be appended as either "true" or "false", and for numeric types the string will be a decimal representation of the value. For example:
StringBuffer buf = new StringBuffer("The number is "); long number = 999; buf.append(number);
will result in buf containing the string "The number is 999".
There is nothing to prevent you from appending constants to a StringBuffer object. For example, if you now execute the statement:
the object buf will contain "The number is 99912.34".
There is also a version of the append() method which accepts an array of type char as an argument. The contents of the array are appended to the StringBuffer object as a string. A further variation on this enables you to append a subset of the elements from an array of type char by using two additional arguments: one to specify the index of the first element to be appended, and another to specify the total number of elements to be appended. An example of how you might use this is as follows;
This will append the string "exactly" to buf, so after executing this statement buf will contain "The number is 99912.34 exactly".
You may be somewhat bemused by the plethora of append() method options, so let's collect all the possibilities together. You can append any of the following types to a StringBuffer object:
You can also append an array of type char, and a subset of the elements of an array of type char. In each case the String equivalent of the argument is appended to the string in the StringBuffer object.
We haven't discussed type Object - it is here for the sake of completeness. You will learn about this type of object in Chapter 6.
To insert a string into a StringBuffer object, you use the insert() method of the object. The first argument specifies the index of the position in the object where the first character is to be inserted. For example, if buf contains the string "Many hands make light work", the statement:
buf.insert(4, " old");
will insert the string "old" starting at index position 4, so buf will contain the string "Many old hands make light work" after executing this statement.
There are many versions of the insert() method that will accept a second argument of any of the same range of types that apply to the append() method, so you can use any of the following with the insert() method:
In each case the string equivalent of the second argument is inserted starting at the index position specified by the first argument. You can also insert an array of type char, and if you need to insert a subset of an array of type char into a StringBuffer object, you can call the version of insert() that accepts four arguments:
insert(int index, char str, int offset, int length)
Inserts a substring into the StringBuffer object starting at position index. The substring is the String representation of length characters from the str array, starting at position offset.
If the value of index is outside the range of the string in the StringBuffer object, or the offset or length values result in illegal indexes for the array, str, then an exception of type StringIndexOutOfBoundsException will be thrown.
The StringBuffer includes the charAt() and getChars() methods, both of which work in the same way as the methods of the same name in the class String which we've already seen. The charAt() method extracts the character at a given index position, and the getChars() method extracts a range of characters and stores them in an array of type char starting at a specified index position. You should note that there is no equivalent to the getBytes() method for StringBuffer objects.
You can change a single character in a StringBuffer object by using the setCharAt() method. The first argument indicates the index position of the character to be changed, and the second argument specifies the replacement character. For example, the statement:
will set the fourth character in the string to 'Z'.
You can completely reverse the sequence of characters in a StringBuffer object with the reverse() method. For example, if you define the object with the declaration:
StringBuffer palindrome = new StringBuffer("so many dynamos");
you can then transform it with the statement:
which will result in palindrome containing the useful phrase "somanyd ynam os".
You can produce a String object from a StringBuffer object by using the toString() method of the StringBuffer class. This method creates a new String object and initializes it with the string contained in the StringBuffer object. For example, to produce a String object containing the proverb that we created in the previous section, you could write:
String saying = proverb.toString();
The object saying will contain "Many hands make light work".
The toString() method is used extensively by the compiler together with the append() method to implement the concatenation of String objects. When you write a statement such as:
String saying = "Many" + " hands" + " make" + " light" + " work";
the compiler will implement this as:
String saying = new StringBuffer().append("Many").append(" hands"). append(" make").append(" light"). append(" work").toString();
The expression to the right of the = sign is executed from left to right, so the segments of the string are appended to the StringBuffer object that is created until finally the toString() method is invoked to convert it to a String object. String objects can't be modified, so any alteration or extension of a String object will involve the use of a StringBuffer object, which can be changed.