Think Java: How to Think Like a Computer Scientist by Allen B. Downey - HTML preview

PLEASE NOTE: This is an HTML preview only and some elements such as links or page numbers may be incorrect.
Download the book in PDF, ePub, Kindle for a complete version.

Chapter 8

Strings and things

8.1

Invoking methods on objects

In Java and other object-oriented languages, objects are collections of related

data that come with a set of methods. These methods operate on the objects,

performing computations and sometimes modifying the object’s data.

Strings are objects, so you might ask “What is the data contained in a

String object?”

and “What are the methods we can invoke on String

objects?”

The components of a String object are letters or, more generally, characters.

Not all characters are letters; some are numbers, symbols, and other things.

For simplicity I call them all letters.

There are many methods, but I use only a few in this book. The rest are

documented at http://download.oracle.com/javase/6/docs/api/java/

lang/String.html.

The first method we will look at is charAt, which allows you to extract letters

from a String. to store the result, we need a new type: char is the variable

type that can store individual characters (as opposed to strings).

chars work just like the other types we have seen:

char bob = ✬c✬;

if (bob == ✬c✬) {

92

Chapter 8. Strings and things

System.out.println(bob);

}

Character values appear in single quotes, like ’c’.

Unlike string values

(which appear in double quotes), character values can contain only a sin-

gle letter or symbol.

Here’s how the charAt method is used:

String fruit = "banana";

char letter = fruit.charAt(1);

System.out.println(letter);

fruit.charAt() means that I am invoking the charAt method on the object

named fruit. I am passing the argument 1 to this method, which means

that I want to know the first letter of the string. The result is a character,

which is stored in a char named letter. When I print the value of letter,

I get a surprise:

a

a is not the first letter of "banana". Unless you are a computer scientist.

For perverse reasons, computer scientists start counting from zero. The 0th

letter (“zeroeth”) of "banana" is b. The 1th letter (“oneth”) is a and the

2th (“twooth”) letter is n.

If you want the zereoth letter of a string, you have to pass 0 as an argument:

char letter = fruit.charAt(0);

8.2

Length

The next String method we’ll look at is length, which returns the number

of characters in the string. For example:

int length = fruit.length();

length takes no arguments and returns an integer, in this case 6. Notice

that it is legal to have a variable with the same name as a method (although

it can be confusing for human readers).

To find the last letter of a string, you might be tempted to try something

like

8.3. Traversal

93

int length = fruit.length();

char last = fruit.charAt(length);

// WRONG!!

That won’t work. The reason is that there is no 6th letter in "banana".

Since we started counting at 0, the 6 letters are numbered from 0 to 5. To

get the last character, you have to subtract 1 from length.

int length = fruit.length();

char last = fruit.charAt(length-1);

8.3

Traversal

A common thing to do with a string is start at the beginning, select each

character in turn, do some computation with it, and continue until the end.

This pattern of processing is called a traversal. A natural way to encode a

traversal is with a while statement:

int index = 0;

while (index < fruit.length()) {

char letter = fruit.charAt(index);

System.out.println(letter);

index = index + 1;

}

This loop traverses the string and prints each letter on a line by itself. Notice

that the condition is index < fruit.length(), which means that when

index is equal to the length of the string, the condition is false and the body

of the loop is not executed. The last character we access is the one with the

index fruit.length()-1.

The name of the loop variable is index. An index is a variable or value used

to specify a member of an ordered set, in this case the string of characters.

The index indicates (hence the name) which one you want.

Exercise 8.1. Write a method that takes a String as an argument and that

prints the letters backwards all on one line.

8.4

Run-time errors

Way back in Section 1.3.2 I talked about run-time errors, which are errors

that don’t appear until a program has started running. In Java run-time

94

Chapter 8. Strings and things

errors are called exceptions.

You probably haven’t seen many run-time errors, because we haven’t been do-

ing many things that can cause one. Well, now we are. If you use the charAt

method and provide an index that is negative or greater than length-1, it

throws an exception. You can think of “throwing” an exception like throw-

ing a tantrum.

When that happens, Java prints an error message with the type of exception

and a stack trace, which shows the methods that were running when the

exception occurred. Here is an example:

public class BadString {

public static void main(String[] args) {

processWord("banana");

}

public static void processWord(String s) {

char c = getLastLetter(s);

System.out.println(c);

}

public static char getLastLetter(String s) {

int index = s.length();

// WRONG!

char c = s.charAt(index);

return c;

}

}

Notice the error in getLastLetter: the index of the last character should

be s.length()-1. Here’s what you get:

Exception in thread "main" java.lang.StringIndexOutOfBoundsException:

String index out of range: 6

at java.lang.String.charAt(String.java:694)

at BadString.getLastLetter(BadString.java:24)

at BadString.processWord(BadString.java:18)

at BadString.main(BadString.java:14)

8.5. Reading documentation

95

Then the program ends. The stack trace can be hard to read, but it contains

a lot of information.

Exercise 8.2. Read the stack trace and answer these questions:

❼ What kind of Exception occurred, and what package is it defined in?

❼ What is the value of the index that caused the exception?

❼ What method threw the exception, and where is that method defined?

❼ What method invoked charAt?

❼ In BadString.java, what is the line number where charAt was in-

voked?

8.5

Reading documentation

If you go to http://download.oracle.com/javase/6/docs/api/java/

lang/String.html. and click on charAt, you get the following documenta-

tion (or something like it):

public char charAt(int index)

Returns the char value at the specified index. An index ranges from 0

to length() - 1. The first char value of the sequence is at index 0,

the next at index 1, and so on, as for array indexing.

Parameters: index - the index of the char value.

Returns: the char value at the specified index of this string. The

first char value is at index 0.

Throws: IndexOutOfBoundsException - if the index argument is negative

or not less than the length of this string.

The first line is the method’s prototype, which specifies the name of the

method, the type of the parameters, and the return type.

The next line describes what the method does. The following lines explain the

parameters and return values. In this case the explanations are redundant,

96

Chapter 8. Strings and things

but the documentation is supposed to fit a standard format. The last line

describes the exceptions this method might throw.

It might take some time to get comfortable with this kind of documentation,

but it is worth the effort.

8.6

The indexOf method

indexOf is the inverse of charAt: charAt takes an index and returns the

character at that index; indexOf takes a character and finds the index where

that character appears.

charAt fails if the index is out of range, and throws an exception. indexOf

fails if the character does not appear in the string, and returns the value -1.

String fruit = "banana";

int index = fruit.indexOf(✬a✬);

This finds the index of the letter ’a’ in the string. In this case, the letter

appears three times, so it is not obvious what indexOf should do. According

to the documentation, it returns the index of the first appearance.

To find subsequent appearances, there is another version of indexOf. It takes

a second argument that indicates where in the string to start looking. For

an explanation of this kind of overloading, see Section 6.4.

If we invoke:

int index = fruit.indexOf(✬a✬, 2);

it starts at the twoeth letter (the first n) and finds the second a, which is at

index 3. If the letter happens to appear at the starting index, the starting

index is the answer. So

int index = fruit.indexOf(✬a✬, 5);

returns 5.

8.7

Looping and counting

The following program counts the number of times the letter ’a’ appears in

a string:

8.8. Increment and decrement operators

97

String fruit = "banana";

int length = fruit.length();

int count = 0;

int index = 0;

while (index < length) {

if (fruit.charAt(index) == ✬a✬) {

count = count + 1;

}

index = index + 1;

}

System.out.println(count);

This program demonstrates a common idiom, called a counter. The variable

count is initialized to zero and then incremented each time we find an ’a’

(to increment is to increase by one; it is the opposite of decrement, and

unrelated to excrement, which is a noun). When we exit the loop, count

contains the result: the total number of a’s.

Exercise 8.3. Encapsulate this code in a method named countLetters, and

generalize it so that it accepts the string and the letter as arguments.

Then rewrite the method so that it uses indexOf to locate the a’s, rather than

checking the characters one by one.

8.8

Increment and decrement operators

Incrementing and decrementing are such common operations that Java pro-

vides special operators for them.

The ++ operator adds one to the cur-

rent value of an int or char. -- subtracts one. Neither operator works on

doubles, booleans or Strings.

Technically, it is legal to increment a variable and use it in an expression at

the same time. For example, you might see something like:

System.out.println(i++);

Looking at this, it is not clear whether the increment will take effect before or

after the value is printed. Because expressions like this tend to be confusing,

I discourage you from using them. In fact, to discourage you even more, I’m

98

Chapter 8. Strings and things

not going to tell you what the result is. If you really want to know, you can

try it.

Using the increment operators, we can rewrite the letter-counter:

int index = 0;

while (index < length) {

if (fruit.charAt(index) == ✬a✬) {

count++;

}

index++;

}

It is a common error to write something like

index = index++;

// WRONG!!

Unfortunately, this is syntactically legal, so the compiler will not warn you.

The effect of this statement is to leave the value of index unchanged. This

is often a difficult bug to track down.

Remember, you can write index = index+1, or you can write index++, but

you shouldn’t mix them.

8.9

Strings are immutable

As you read the documentation of the String methods, you might notice

toUpperCase and toLowerCase. These methods are often a source of confu-

sion, because it sounds like they have the effect of changing (or mutating) an

existing string. Actually, neither these methods nor any others can change a

string, because strings are immutable.

When you invoke toUpperCase on a String, you get a new String as a

return value. For example:

String name = "Alan Turing";

String upperName = name.toUpperCase();

After the second line is executed, upperName contains the value "ALAN

TURING", but name still contains "Alan Turing".

8.10. Strings are incomparable

99

8.10

Strings are incomparable

It is often necessary to compare strings to see if they are the same, or to see

which comes first in alphabetical order. It would be nice if we could use the

comparison operators, like == and >, but we can’t.

To compare Strings, we have to use the equals and compareTo methods.

For example:

String name1 = "Alan Turing";

String name2 = "Ada Lovelace";

if (name1.equals (name2)) {

System.out.println("The names are the same.");

}

int flag = name1.compareTo (name2);

if (flag == 0) {

System.out.println("The names are the same.");

} else if (flag < 0) {

System.out.println("name1 comes before name2.");

} else if (flag > 0) {

System.out.println("name2 comes before name1.");

}

The syntax here is a little weird. To compare two Strings, you have to

invoke a method on one of them and pass the other as an argument.

The return value from equals is straightforward enough; true if the strings

contain the same characters, and false otherwise.

The return value from compareTo is a weird, too. It is the difference between

the first characters in the strings that differ. If the strings are equal, it is 0.

If the first string (the one on which the method is invoked) comes first in the

alphabet, the difference is negative. Otherwise, the difference is positive. In

this case the return value is positive 8, because the second letter of “Ada”

comes before the second letter of “Alan” by 8 letters.

Just for completeness, I should admit that it is legal, but very seldom correct,

to use the == operator with Strings. I explain why in Section 13.4; for now,

don’t do it.

100

Chapter 8. Strings and things

8.11

Glossary

object: A collection of related data that comes with a set of methods that

operate on it. The objects we have used so far are Strings, Bugs,

Rocks, and the other GridWorld objects.

index: A variable or value used to select one of the members of an ordered

set, like a character from a string.

exception: A run-time error.

throw: Cause an exception.

stack trace: A report that shows the state of a program when an exception

occurs.

prototype: The first line of a method, which specifies the name, parameters

and return type.

traverse: To iterate through all the elements of a set performing a similar

operation on each.

counter: A variable used to count something, usually initialized to zero and

then incremented.

increment: Increase the value of a variable by one. The increment operator

in Java is ++.

decrement: Decrease the value of a variable by one. The decrement oper-

ator in Java is --.

8.12

Exercises

Exercise 8.4. The purpose of this exercise is to review encapsulation and

generalization.

1. Encapsulate the following code fragment, transforming it into a method

that takes a String as an argument and that returns the final value of

count.

8.12. Exercises

101

2. In a sentence or two, describe what the resulting method does (without

getting into the details of how).

3. Now that you have generalized the code so that it works on any String,

what could you do to generalize it more?

String s = "((3 + 7) * 2)";

int len = s.length();

int i = 0;

int count = 0;

while (i < len) {

char c = s.charAt(i);

if (c == ✬(✬) {

count = count + 1;

} else if (c == ✬)✬) {

count = count - 1;

}

i = i + 1;

}

System.out.println(count);

Exercise 8.5. The point of this exercise is to explore Java types and fill in

some of the details that aren’t covered in the chapter.

1. Create a new program named Test.java and write a main method that

contains expressions that combine various types using the + operator.

For example, what happens when you “add” a String and a char?

Does it perform addition or concatenation? What is the type of the

result? (How can you determine the type of the result?)

2. Make a bigger copy of the following table and fill it in. At the intersec-

tion of each pair of types, you should indicate whether it is legal to use

the + operator with these types, what operation is performed (addition

or concatenation), and what the type of the result is.

102

Chapter 8. Strings and things

boolean

char

int

String

boolean

char

int

String

3. Think about some of the choices the designers of Java made when they

filled in this table. How many of the entries seem unavoidable, as if

there were no other choice? How many seem like arbitrary choices

from several equally reasonable possibilities? How many seem stupid?

4. Here’s a puzzler: normally, the statement x++ is exactly equivalent to

x = x + 1. But if x is a char, it’s not! In that case, x++ is legal, but

x = x + 1 causes an error. Try it out and see what the error message

is, then see if you can figure out what is going on.

Exercise 8.6. What is the output of this program? Describe in a sentence

what mystery does (not how it works).

public class Mystery {

public static String mystery(String s) {

int i = s.length() - 1;

String total = "";

while (i >= 0 ) {

char ch = s.charAt(i);

System.out.println(i + "

" + ch);

total = total + ch;

i--;

}

return total;

}

public static void main(String[] args) {

System.out.println(mystery("Allen"));

}

}

8.12. Exercises

103

Exercise 8.7. A friend of yours shows you the following method and explains

that if number is any two-digit number, the program will output the number

backwards. He claims that if number is 17, the method will output 71.

Is he right? If not, explain what the program actually does and modify it so

that it does the right thing.

int number = 17;

int lastDigit = number%10;

int firstDigit = number/10;

System.out.println(lastDigit + firstDigit);

Exercise 8.8. What is the output of the following program?

public class Enigma {

public static void enigma(int x) {

if (x == 0) {

return;

} else {

enigma(x/2);

}

System.out.print(x%2);

}

public static void main(String[] args) {

enigma(5);

System.out.println("");

}

}

Explain in 4-5 words what the method enigma really does.

Exercise 8.9.

1. Create a new program named Palindrome.java.

2. Write a method named first that takes a String and returns the first

letter, and one named last that returns the last letter.

3. Write a method named middle that takes a String and returns a sub-

string that contains everything except the first and last characters.

104

Chapter 8. Strings and things

Hint: read the documentation of the substring method in the String

class. Run a few tests to make sure you understand how substring

works before you try to write middle.

What happens if you invoke middle on a string that has only two let-

ters? One letter? No letters?

4. The usual definition of a palindrome is a word that reads the same

both forward and backward, like “otto” and “palindromeemordnilap.”

An alternative way to define a property like this is to specify a way of

testing for the property. For example, we might say, “a single letter is

a palindrome, and a two-letter word is a palindrome if the letters are

the same, and any other word is a palindrome if the first letter is the

same as the last and the middle is a palindrome.”

Write a recursive method named isPalindrome that takes a String

and that returns a boolean indicating whether the word is a palindrome

or not.

5. Once you have a working palindrome checker, look for ways to simplify

it by reducing the number of conditions you check. Hint: it might be

useful to adopt the definition that the empty string is a palindrome.

6. On a piece of paper, figure out a strategy for checking palindromes iter-

atively. There are several possible approaches, so make sure you have

a solid plan before you start writing code.

7. Implement your strategy in a method called isPalindromeIter.

8. Optional: Appendix B provides code for reading a list of words from a

file. Read a list of words and print the palindromes.

Exercise 8.10. A word is said to be “abecedarian” if the letters in the word

appear in alphabetical order. For example, the following are all 6-letter En-

glish abecedarian words.

abdest, acknow, acorsy, adempt, adipsy, agnosy, befist, behint,

beknow, bijoux, biopsy, cestuy, chintz, deflux, dehors, dehort,

deinos, diluvy, dimpsy

8.12. Exercises

105

1. Describe a process for checking whether a given word (String) is

abecedarian, assuming that the word contains only lower-case letters.

Your process can be iterative or recursive.

2. Implement your process in a method called isAbecedarian.

Exercise 8.11. A dupledrome is a word that contains only double letters, like

“llaammaa” or “ssaabb”. I conjecture that there are no dupledromes in com-

mon English use. To test that conjecture, I would like a program that reads

words from the dictionary one at a time and checks them for dupledromity.

Write a method called isDupledrome that takes a String and returns a

boolean indicating whether the word is a dupledrome.

Exercise 8.12.

1. The Captain Crunch decoder ring works by taking each

letter in a string and adding 13 to it. For example, ’a’ becomes ’n’ and

’b’ becomes ’o’. The letters “wrap around” at the end, so ’z’ becomes

’m’.

Write a method that takes a String and that returns a new String con-

taining the encoded version. You should assume that the String con-

tains upper and lower case letters, and spaces, but no other punctuation.

Lower case letters should be tranformed into other lower case letters;

upper into upper. You should not encode the spaces.

2. Generalize the Captain Crunch method so that instead of adding 13 to

the letters, it adds any given amount. Now you should be able to encode

things by adding 13 and decode them by adding -13. Try it.

Exercise 8.13. If you did the GridWorld exercises in Chapter 5, you might

enjoy this exercise. The