StringBuffer & StringBuilder: Building a Mutable String in Java

Building a Mutable String in Java Using StringBuilder



The video, below, will give you a good review of basic String methods in java. More importantly, the last portion of the video will introduce you to Java's StringBuilder class.



Activity Guide for StringBuilder

Purpose

This lab was donated by Ria Galanos. In this StringBuilder lab, you will build a class using instances of another core class as components. You will also have further opportunities to practice writing conditional and while statements.

The game Hangman


We will implement a class for playing a variation of the word-guessing game hangman. The game is played as follows.

A word is given with the letters replaced with dashes. For instance, if the word is "gobble" the following is displayed:

        ------

The player guesses a letter. If the letter is in the word, its location is shown. For instance, if the player guesses `e' the following is displayed:

        -----e

If the letter is not in the word, it is added to a list of "misses." For instance, if the player next guesses `a' the following is displayed:

        -----e missed: a

If there are multiple occurrences of a letter in the word, all occurrences are shown. For instance, if the player next guesses `b' the following is displayed:

        --bb-e missed: a

Guessing a letter that has already been guessed counts as a miss. For instance, if the player again guesses `b' the following is displayed:

        --bb-e missed: ab

There is a maximum number of misses allowed. Play continues until either the player guesses all the letters or reaches the maximum number of misses.

The class Hangman


We will build a model class for the game. The specifications are given below.

        public class Hangman
            The word-guessing game hangman.
         
            public Hangman (String word, int maxMisses)
                Create a new game with the specified word to be guessed.
                maxMisses is the maximum number of misses allowed.
                Require:
                    maxMisses > 0
         
            public String word ()
                The word to be guessed.
         
            public int maxMissesAllowed ()
                The maximum number of misses allowed.
         
            public void guess (char letter)
                Guess the specified letter.
         
            public String correct ()
                The word with the correctly guessed letters shown. Letters not
                yet guessed are shown as hyphens (-).
         
            public String lettersMissed ()
                Erroneous guesses. Letters either are not in the word, or guessed more than once.
         
            public boolean gameOver ()
                The game is over.
         
            public boolean gameWon ()
                The game has been won.

The Class StringBuilder

To implement the class Hangman, we will use the standard class StringBuilder, defined in java.lang. A StringBuilder is essentially a character string that can be modified. (We could use String for the exercise, but a StringBuilder gives a little more efficient implementation. There is another standard class, StringBuffer, that has the same set of features as StringBuilder. StringBuilder is more efficient than StringBuffer and is preferred in most common situations.)

Create two StringBuilders in the interactions pane, composed of identical character strings.

        > StringBuilder b1 = new StringBuilder("abc");
        > StringBuilder b2 = new StringBuilder("abc");
        > System.out.println(b1);
        > System.out.println(b2);

Unlike Strings, StringBuilders composed of the same sequence of characters are not equal.

        > System.out.println(b1.equals(b2));    // not the right way to compare StringBuilders
> System.out.println(b1 == b2); // also not the right way to compare StringBuilders
        > System.out..println(b1.toString().equals(b2.toString()));

The method length gives the number of characters in the string.

        > b1.length()

Constructing a StringBuilder with an integer argument gives an empty string with the specified capacity: i.e., the number of characters it can contain.

        > StringBuilder b3 = new StringBuilder(3);
        > System.out.println(b3);
        > System.out..println(b3.length());
        > System.out.println(b3.capacity());

The method append(char) adds the specified character to the end of the string.

        > b3.append('-');
        > System.out.println(b3.length());
> System.out.println(b3.capacity());

If the capacity of a StringBuilder is exceeded, it is automatically expanded.

        > b3.append('-');
        > b3.append('-');
        > System.out.println(b3.length());
> System.out.println(b3.capacity());
        > b3.append('-');
        > System.out.println(b3.length());
> System.out.println(b3.capacity());

Characters can be located by index. The first character in the string is at "index 0." The method charAt(int) gives the character at the specified index position.

        > System.out.println(b1.charAt(0));
        > System.out.println(b1.charAt(2));

Note that the method charAt has a precondition requiring its argument to be a legal index.

        > System.out.println(b1.charAt(3));

The method setCharAt(int,char) can be used to change the character at the specified index position.

        > b1.setCharAt(0,'x');
        > System.out.println(b1);

Does this method have the same precondition as charAt?

The method indexOf(String) finds a substring in the StringBuilder and returns its (starting) index.

        > System.out.println(b2.indexOf("bc"));
        > System.out.println(b2.indexOf("a"));

If the substring does not occur, it returns -1.

        > System.out.println(b2.indexOf("bcd"));

indexOf cannot be invoked with a char argument. But String can be created either with the concatenation operator + or with the method Character.toString(char).

        > System.out.println(b2.indexOf(""+'a'));
        > System.out.println(b2.indexOf(Character.toString('a')));

The class StringBuilder has a number of additional features, but these will be adequate for our needs.

Implementing Hangman

In the IDE of your choice, create the files Hangman.java and HangmanTest.java. 

Let's start with a test case. We'll need one "typical" game. Make sure that the word you choose for the game has multiple instances of the same letter. Something like "aardvark" will do. Choose a relatively small number (three or four) for maximum misses. We also want some minimal games. Since the specs don't prohibit it, a game with the empty word should be tested, and a game with a one letter word and only one miss allowed.

In HangmanTest, declare three instance variables and initialize them with three games as described above.

Write a test for the initial state. Testing the initial state of one of the games should be adequate. Test that word and maxMissesAllowed return the proper values. Test that correct returns a string of hyphens and that lettersMissed returns the empty string.
Save, compile, and test. The test should, of course, fail.

Now let's look at the Hangman class. We will maintain the state of the game with three StringBuilders. One will keep the list of misses. One will keep the word with unguessed letters as hyphens. (This is the string returned by correct.) One will keep the word with guessed letters replaced by hyphens. (This is essentially the "opposite" of correct. When the player makes a guess, this is where we check for a match.) It will also be useful to keep the word as a String, the number of misses, and the maximum number of misses allowed.

Declare Hangman instance variables as described above. Implement the Hangman constructor, and the trivial methods word, maxMissesAllowed, correct, and letterMissed.

Save, compile, and test. Correct your implementation until the test is successful.

Be sure to test a guess for a character that occurs multiple times, a guess for a character that does not occur, a guess for a character that occurs but has already been guessed, and a guess for a character that does not occur and has already been guessed.

Implement the method guess. The method should first check to make sure that the maximum number of misses has not been reached. Remember that a letter might have multiple occurrence, all of which should be replaced. (Think loop.)

Finally, we need to test gameOver and gameWon. Write two tests, one to test games that have been won and one to test games that have been lost. Save and compile.

Implement method gameOver and gameWon. Save, compile, and test.

What to submit

If directed by your instructor, turn in listings of Hangman and HangmanTest. Your instructor might also require that you turn in the interactions history. You can save the interactions history by selecting the Save Interactions History... option from the Tools pull-down menu. Be sure everything you turn in is clearly labeled with your name and section number.