Chapter 7: String Manipulations
In the early 1960s, a message typed on one company’s computer could arrive on another company’s machine as nonsense. The trouble was not the wires. Every manufacturer had invented its own private table deciding which number stood for the letter A, which for a comma, which for a blank space. Two machines could pass numbers to each other perfectly and still disagree completely about what those numbers meant.
The answer was not a cleverer machine. It was an agreement. In 1963 an American standards committee published a single shared table, the American Standard Code for Information Interchange (ASCII), fixing one number for each letter, digit, punctuation mark and control character. Once everyone accepted the same table, text became something that could be stored, sent and compared by machines that had never been designed to work together.
That agreement is still underneath every program you write. When your Java program asks whether one word comes before another in a dictionary, it is not reading the words. It is comparing numbers, in an order that people fixed on paper long before your machine existed.
This chapter is about working with those characters and words in Java — testing them, joining them, splitting them, searching them and rearranging them.
Diagram
Fig. 7.0-1 – flow: the word “CAT” typed on Machine A → encoded as the numbers 67 65 84 using Machine A’s private code table → sent over a link → decoded by Machine B using a different private code table → printed as meaningless symbols; below this, the same journey repeated with one shared code table on both sides, printing “CAT” correctly; label the two paths “different tables” and “one agreed table”
Each of these questions is answered somewhere in this chapter. Read on, and see whether your first guess survives.
7.1 Primitive Values and Wrapper Classes
Before a program can work with words, it has to work with the smaller things words are made of — single characters and single numbers. Java gives you both in two very different forms, and knowing which form you are holding decides what you are allowed to do with it.
You already know the eight primitive types from Class XI: byte, int, short, long, float, double, boolean and char. A primitive type is a type built into the language itself; a variable of such a type holds one plain value and nothing else.
That “nothing else” is the point of this section.
7.1.1 Why a Primitive Value Is Sometimes Not Enough
Think about a school fee counter. The clerk types 450 and the program has to multiply it by the number of months. What the program actually receives from the keyboard is not the number four hundred and fifty. It is the text "450" — three characters sitting side by side.
You cannot multiply text. Somewhere, something has to look at those three characters and work out the whole number they stand for. Where should that piece of skill live?
It cannot live in the int type. An int variable is a labelled box with a value in it; it carries no instructions and can be asked no questions. Ask it what the largest value an int can hold is, and there is nobody to ask.
In Java, skill lives in classes. So for each primitive type Java also provides a class whose objects hold one value of that type — and, because it is a class, it can also carry the methods and constants that go with that type. These are the wrapper classes.
The name is a picture of what happens: the plain value is wrapped inside an object, the way a sweet is wrapped in paper. The value is unchanged; what it gains is a covering that other parts of Java know how to handle.
Look at the difference in memory, shown in Fig. 7.1-1. A primitive variable is a single box. A wrapper object is a box with a value inside it, reached through a variable that only points to it.
Diagram
Fig. 7.1-1 – two labelled memory pictures side by side; on the left, a single box labelled int n containing the value 45; on the right, a small box labelled Integer obj holding an arrow that points to a larger box drawn as an object, with the value 45 inside it and the method names intValue and toString listed below the value; caption the two pictures “a value” and “an object holding a value”
Two things follow from this picture, and both matter later in the chapter. First, the wrapper object can be asked to do work. Second, wherever Java insists on an object, a wrapped value will be accepted and a bare primitive value will not.
7.1.2 The Eight Wrapper Classes
There is exactly one wrapper class for each primitive type — eight types, eight classes. Table 7.1 lists them in the same order as the primitive types you learnt in Class XI.
| Primitive type | Wrapper class | Value held |
|---|---|---|
byte |
Byte |
a very small whole number |
int |
Integer |
a whole number |
short |
Short |
a small whole number |
long |
Long |
a large whole number |
float |
Float |
a number with a fractional part |
double |
Double |
a more precise number with a fractional part |
boolean |
Boolean |
true or false |
char |
Character |
a single character |
Read the two middle columns together and one rule stands out. Every primitive type name is written entirely in small letters, because it is a keyword of the language. Every wrapper class name begins with a capital letter, because it is a class. int and Integer are not two spellings of one thing; they are a keyword and a class.
Notice also that two of the names are not simply the primitive name with a capital letter. The wrapper for int is Integer, and the wrapper for char is Character.
All eight classes belong to the package java.lang, which every Java program imports automatically. You never write an import statement to use them.
Six of the eight — Byte, Short, Integer, Long, Float and Double — hold numbers, and all six are grouped under a common class named Number. Character and Boolean hold values that are not numbers, and they stand outside that grouping.
Because a wrapper class is a class, it can carry constants as well as methods. Integer.MAX_VALUE and Integer.MIN_VALUE name the largest and smallest values an int can hold. That is knowledge about the type int — and it had to be kept somewhere, because the type itself cannot hold it.
| Feature | Primitive value (int n) |
Wrapper object (Integer obj) |
|---|---|---|
| What the variable holds | the value itself | a reference to an object that holds the value |
| Methods available | none | methods such as intValue() and toString() |
| Constants available | none | constants such as Integer.MAX_VALUE |
| Accepted where an object is required | no | yes |
| Memory used | just the value | the object, plus the reference to it |
| Speed of ordinary arithmetic | faster | slower, since the value must be taken out first |
The last two rows are the honest limitation. A wrapper object costs more memory and more time than a plain value. So the working rule for the rest of this book is simple: use the primitive type for ordinary calculation, and reach for the wrapper class when you need what only a class can give.
7.1.3 Creating a Wrapper Object
A wrapper object is created like any other object, with new and a constructor that takes the value to be wrapped.
Integer count = new Integer(45);
Character grade = new Character('A');
Double price = new Double(12.5);To get the plain value back out, you call a method on the object. Each wrapper class provides one named after the primitive type it holds.
int c = count.intValue();
char g = grade.charValue();
double p = price.doubleValue();The next program does nothing clever. It only makes visible that the value going in and the value coming out are the same, and that the object can answer a question the primitive could not.
7.1.4 Autoboxing and Unboxing
Writing new Integer(...) every time a value has to become an object is tiresome, and taking it out again with intValue() is no better. From Java 5.0 onwards the compiler does both conversions for you when it can see that they are needed.
Wrapping happens automatically on assignment to a wrapper variable:
Integer p = 32;
Character ch = 'A';Unwrapping happens automatically on assignment to a primitive variable:
Integer n = 45;
int t = n;These two automatic conversions have names.
Nothing new happens inside the machine. The compiler simply writes the longer form on your behalf, as Fig. 7.1-2 shows.
Diagram
Fig. 7.1-2 – two horizontal arrows between two labelled boxes; the upper arrow runs from a box labelled “primitive value int 32” to a box labelled “wrapper object Integer holding 32” and is labelled “autoboxing”; the lower arrow runs back from the object box to the primitive box and is labelled “unboxing”; alongside each arrow print the statement that triggers it, Integer p = 32; above and int t = p; below
Because unboxing is automatic, a wrapped value can even be used directly in arithmetic:
Integer marks = 78;
int total = marks + 12;Here the compiler unboxes marks to 78, adds 12, and stores 90 in total. The convenience is real, but it hides a genuine trap.
7.2 The Character Class
Almost every interesting question about a word is really a question about its characters. Is this token a number or a name? Where does one word end and the next begin? Should this letter be printed in capitals?
Java keeps the tools for answering such questions in the wrapper class Character. This section is about those tools, and about the numeric codes that make them possible.
7.2.1 Characters and Their Numeric Codes
A char variable holds one character, written between single quotes:
char grade = 'A';
char digit = '7';
char blank = ' ';Inside the machine, though, there is no such thing as a letter. There are only numbers. So each character is stored as the number that the agreed code table gives it — 65 for 'A', 55 for '7', 32 for the blank space.
Java uses Unicode, a code table designed to cover the writing systems of the world. Its first 128 codes are exactly the codes of ASCII, which is why the values in Table 7.4 are the ones you will meet in every Class XII program.
| Group of characters | First | Last |
|---|---|---|
Tab '\t' |
9 | 9 |
Newline '\n' |
10 | 10 |
Space ' ' |
32 | 32 |
Digits '0' to '9' |
48 | 57 |
Capital letters 'A' to 'Z' |
65 | 90 |
Small letters 'a' to 'z' |
97 | 122 |
Three facts in this table do most of the work in this chapter, and Fig. 7.2-1 shows why.
Diagram
Fig. 7.2-1 – a horizontal number line from 0 to 127 with four shaded bands marked on it: a narrow band at 32 labelled “space”, a band from 48 to 57 labelled “digits 0–9”, a band from 65 to 90 labelled “capitals A–Z”, and a band from 97 to 122 labelled “small a–z”; draw a double-headed arrow between 65 and 97 labelled “gap of 32”, and mark the codes 9 and 10 near the left end as “tab” and “newline”
First, each group is unbroken and in order. 'B' follows 'A', 'C' follows 'B', and the digit characters run from '0' up to '9' without a gap. That is what makes it possible to test whether a character is a letter by comparing codes.
Second, the same letter in the two cases has two different codes, and the difference is always 32. 'A' is 65 and 'a' is 97; 'Z' is 90 and 'z' is 122.
Third — and students trip on this every year — the digit character '7' is not the number 7. It carries the code 55. A character typed at the keyboard is a symbol, not a quantity, and turning one into the other takes work you will do yourself in section 7.2.4.
7.2.2 Testing a Character
You could write these tests yourself by comparing codes, and later in this chapter you will see why that is sometimes worth doing. But the Character class already carries them, correctly written and clearly named.
Each of these methods is called on the class itself rather than on an object, so the class name is written in front of it — Character.isDigit(ch). Each takes one char and returns a boolean.
| Method | Returns true when the character is |
Example that returns true |
|---|---|---|
isLetter(char) |
a letter, capital or small | 'k' |
isDigit(char) |
one of the digits '0' to '9' |
'4' |
isLetterOrDigit(char) |
a letter or a digit | 'P' or '9' |
isUpperCase(char) |
a capital letter | 'M' |
isLowerCase(char) |
a small letter | 'm' |
isWhitespace(char) |
a space, a tab or a newline | ' ' |
The last one deserves its own word. Whitespace is any character that occupies space on the line without printing a mark of its own: the blank space, the tab and the newline. It is the whitespace characters that separate one word from the next, so any program that works with words has to be able to recognise them.
Now a small problem to put the tests to work. A vehicle registration code is a mixture of letters, digits and a blank space, and a program checking such a code needs to know how many of each it has been given.
| i | code[i] |
test that succeeds | letters | digits | blanks |
|---|---|---|---|---|---|
| 0 | 'R' |
isLetter | 1 | 0 | 0 |
| 1 | 'J' |
isLetter | 2 | 0 | 0 |
| 2 | '1' |
isDigit | 2 | 1 | 0 |
| 3 | '4' |
isDigit | 2 | 2 | 0 |
| 4 | ' ' |
isWhitespace | 2 | 2 | 1 |
| 5 | 'C' |
isLetter | 3 | 2 | 1 |
| 6 | 'A' |
isLetter | 4 | 2 | 1 |
| 7 | '2' |
isDigit | 4 | 3 | 1 |
| 8 | '8' |
isDigit | 4 | 4 | 1 |
Result: the loop ends with i equal to 9, and the program prints 4, 4 and 1. The trace shows that every character increases exactly one counter, so the three counts must always add up to the number of characters examined — a useful way to check such a program on any input.
7.2.3 Converting a Character
Two more methods of Character change a character rather than test it, and one turns it into text.
| Method | Result |
|---|---|
toUpperCase(char) |
the capital form of the character |
toLowerCase(char) |
the small form of the character |
toString(char) |
the character as a String of length one |
The first two are gentler than their names suggest. They convert only when there is something to convert; a character that is already in the wanted case, or that is not a letter at all, is returned unchanged.
System.out.println(Character.toUpperCase('a'));
System.out.println(Character.toUpperCase('M'));
System.out.println(Character.toUpperCase('7'));prints
A
M
7
The third method matters because a character and a one-character piece of text are not the same thing in Java. 'A' is a char, written between single quotes; "A" is a String, written between double quotes. Character.toString('A') builds the second from the first, which is what you need whenever a character must be joined to a piece of text.
7.2.4 Arithmetic on Characters
Since a character is stored as a number, Java lets you do arithmetic with it. Wherever a char is used in a calculation, its code is used in place of it.
System.out.println('A' + 1);This prints 66, not B. The addition produced an int, and an int prints as a number. To see the character with code 66, you must tell Java to treat that number as a character, using a type cast — a conversion you write yourself, in brackets, in front of the value.
System.out.println((char)('A' + 1));Now the output is B.
Two everyday uses follow from this, and both appear again later in the chapter.
To find the numeric value of a digit character, subtract the code of '0':
char c = '7';
int value = c - '0';'7' is 55 and '0' is 48, so value becomes 7 — the quantity, at last, rather than the symbol.
To change the case of a letter without calling a method, use the gap of 32:
char small = 'd';
char capital = (char)(small - 32);7.3 The String Class
A single character is rarely what a program is really about. Names, addresses, sentences, registration codes, menu choices — all of them are runs of characters that have to be kept together and handled as one thing.
Java keeps such a run in an object of the class String, and gives that class more support from the language than any other class in the library.
7.3.1 What a String Object Holds
A String object holds a fixed sequence of characters, along with the count of how many there are. Text written between double quotes is a string literal, and each literal is itself a String object waiting in the program.
String city = "Jaipur";
String empty = "";The characters of a string are numbered from 0, exactly as the cells of an array are. Fig. 7.3-1 shows the numbering for a nine-character string.
Diagram
Fig. 7.3-1 – the string “RAJASTHAN” drawn as a row of nine equal boxes, one character per box, reading R A J A S T H A N; below each box print its index 0 to 8; above the row write the label “length is 9”; draw a small arrow to the last box labelled “last index is length − 1”
That picture carries the single most useful fact about strings. A string of length 9 has indexes running from 0 to 8, so the last index is always one less than the length. Almost every mistake a student makes with strings is a mistake about that one.
Two warnings about what a String is not.
It is not a primitive type. String is a class, so a String variable holds a reference to an object, not the characters themselves. The language does give it two conveniences no other class receives — you may create one by writing a literal, and you may join two with the + operator — but those are conveniences, not a change of status.
It is also not an array. Even though a string keeps its characters in order and numbers them from 0, you cannot write city[0]. The characters are reached by calling a method, which you will meet in section 7.4.1.
| Feature | char |
String |
|---|---|---|
| What it holds | exactly one character | any number of characters, including none |
| How it is written | between single quotes, 'A' |
between double quotes, "A" |
| Kind of type | primitive type | class |
| Methods available | none on the value itself | many, such as length() |
Result of joining with + |
codes are added, unless text is involved | characters are joined end to end |
The third row is worth reading twice. 'A' and "A" look almost the same on the page and are entirely different things to the compiler: one is a value of a primitive type, the other is an object.
7.3.2 Creating a String Object
There are two ways to bring a String object into existence, and the difference between them is worth understanding once, properly.
The first is to assign a literal:
String subject = "Computer Science";The second is to use new with a constructor of the class:
String subject = new String("Computer Science");Both give you a string with the same sixteen characters, and every method in this chapter behaves identically on the two. What differs is how many objects exist.
Java keeps a single store of the string literals appearing in a program. Write the same literal twice and both variables are pointed at the one stored object. Write new, and Java is being ordered to build a fresh object, so a second one is made even though its contents are identical.
String a = "cat";
String b = "cat";
String c = new String("cat");Here a and b refer to the same object, while c refers to a different object holding the same three characters, as Fig. 7.3-2 shows.
Diagram
Fig. 7.3-2 – three small labelled boxes on the left for the variables a, b and c; arrows from a and b meeting at a single object box on the right containing the characters c a t and labelled “shared literal”; a separate arrow from c to a second object box, also containing c a t, labelled “built by new”
This is not a curiosity. It is the reason that comparing two strings with == sometimes appears to work and sometimes does not, which is settled properly in section 7.4.5. The safe habit starts here: use a literal unless you have a reason not to, and never test two strings for equality with ==.
A third constructor is occasionally useful. Given an array of characters, it builds the string those characters spell:
char letters[] = {'S', 'I', 'T', 'A'};
String name = new String(letters);name now holds "SITA". You will find a use for this in section 7.5, when characters have been rearranged in an array and must be turned back into a word.
7.3.3 Why a String Cannot Be Changed
Here is the property that governs everything else in this chapter. Once a String object exists, the characters inside it never change. A string is immutable.
This sounds harmless until you see what it does to a program.
Fig. 7.3-3 shows what actually happened in the corrected version.
Diagram
Fig. 7.3-3 – a variable box labelled title with a crossed-out arrow to an object containing “annual report” marked “unchanged, now unused”, and a new solid arrow to a second object containing “ANNUAL REPORT” marked “newly built”; label the whole figure “the variable moved; the object did not change”
Why would a language be designed this way? Because a shared object that nobody can alter is a safe object. Since the characters of a literal can never change, Java is free to let several variables share one copy of it, as you saw in section 7.3.2, without any risk that one part of a program will silently damage the text another part is using.
The price is paid whenever a string is built up a piece at a time.
7.4 Operations on Strings
The String class supplies a large set of methods, and a student meeting them as a list has no way of telling which matter. So this section groups them by the question they answer: how long is it, what is inside it, how do I join it, how do I compare it, where is this word, and how do I put something else in its place.
Every one of them obeys the rule established in section 7.3.3. None of them changes the string it is called on. Each returns an answer — a number, a boolean, or a brand-new string — and it is your job to catch it.
7.4.1 Length and Access to Individual Characters
Two methods do almost all the ordinary work.
length() returns the number of characters in the string, as an int. charAt(i) returns the character standing at index i, as a char.
String word = "Jaipur";
System.out.println(word.length());
System.out.println(word.charAt(0));
System.out.println(word.charAt(word.length() - 1));prints
6
J
r
The third line is the standard way to reach the last character, and it is worth memorising as a pattern rather than as a fact. There is no index 6 in a string of length 6, because the indexes are 0 to 5.
Together these two methods let a program walk through a string one character at a time, which is the foundation of nearly every string algorithm in section 7.5.
7.4.2 Extracting a Substring
A substring is any run of consecutive characters taken from a string. The method substring() comes in two forms.
With one argument, s.substring(begin) returns everything from index begin to the end of the string.
With two arguments, s.substring(begin, end) returns the characters from index begin up to but not including index end.
String s = "Computer";
System.out.println(s.substring(3));
System.out.println(s.substring(3, 6));prints
puter
put
The second rule is the one that catches everybody, so look at Fig. 7.4-1 rather than trying to remember words.
Diagram
Fig. 7.4-1 – the string “Computer” drawn as eight boxes with indexes 0 to 7 written below them; two vertical cut marks drawn between boxes, one immediately before the box at index 3 labelled “begin = 3, taken”, the other immediately before the box at index 6 labelled “end = 6, not taken”; shade the three boxes at indexes 3, 4 and 5 and label the shaded run “the result, put”
Read the two arguments as cut positions rather than as characters, and the rule stops feeling arbitrary. The cuts fall before the index you name. That also explains the length of the result, which is always end − begin.
Fixed-format codes are the everyday use of this method. A school stores each book of its library under a code such as MATH-2026-014, in which the first four characters name the subject, the next four give the year of purchase and the last three are the serial number. The positions never move, so the parts can be lifted out by index alone.
7.4.3 Joining Strings
Two strings are joined — concatenated — into a longer one either with the + operator or with the concat() method.
String first = "Computer";
String second = "Science";
System.out.println(first + " " + second);
System.out.println(first.concat(" ").concat(second));Both lines print Computer Science.
The + operator is far more common, for two reasons. It reads naturally when several pieces are joined at once, and it accepts values that are not strings at all — a number, a character or a boolean on either side of a string is turned into text automatically. That is why every System.out.println in this chapter can join a heading to a count without any conversion being written.
int books = 37;
System.out.println("Books issued: " + books);The concat() method has no such generosity. It takes a String and nothing else, so first.concat(37) will not compile.
One habit is worth forming now. Since each join builds a new object, a program that joins in a loop pays the cost measured in section 7.3.3. For a handful of pieces this does not matter at all; for thousands, section 7.7 has the answer.
7.4.4 Changing Case and Removing Blanks
Three methods tidy a string up.
toUpperCase() returns the string with every small letter turned into a capital. toLowerCase() does the reverse. Characters that are not letters — digits, punctuation, spaces — pass through untouched.
trim() returns the string with whitespace removed from the front and the back. Blanks between words are left exactly as they were.
String entry = " Sitaram Verma ";
System.out.println("[" + entry.trim() + "]");
System.out.println(entry.trim().toUpperCase());prints
[Sitaram Verma]
SITARAM VERMA
The square brackets are printed only to make the missing blanks visible; they are not part of the string.
These three methods matter more than they look. Text typed by a person arrives with stray spaces at the ends and in whatever case the typist felt like using. Comparing such text to anything stored in the program is hopeless until it has been trimmed and put into one case — which is the subject of the next subsection.
7.4.5 Comparing Two Strings
Java offers three sensible ways to compare strings, and one tempting way that is wrong.
equals() returns true when the two strings hold exactly the same characters in the same order, treating capitals and small letters as different.
equalsIgnoreCase() asks the same question while treating 'A' and 'a' as matching.
compareTo() answers a bigger question: which of the two comes first. It returns 0 when the strings are identical, a negative number when the string it is called on comes first, and a positive number when it comes later.
String a = "AMAN";
String b = "AMIT";
System.out.println(a.equals(b));
System.out.println(a.compareTo(b));prints
false
-8
Where does −8 come from? compareTo() walks along both strings together. 'A' matches 'A', 'M' matches 'M', and at index 2 it finds 'A' against 'I'. It stops there and returns the difference of their codes, 65 − 73, which is −8. If one string runs out first while everything has matched — as with "car" and "cart" — it returns the difference of the lengths instead.
The sign is what your program should use; the exact size is rarely of interest. A negative result means “comes first in dictionary order”, and since capitals carry smaller codes than small letters, "Zebra" comes before "apple" in Java’s ordering even though it comes later in an English dictionary.
| Feature | == |
equals() |
equalsIgnoreCase() |
compareTo() |
|---|---|---|---|---|
| Question asked | same object? | same characters? | same characters, ignoring case? | which comes first? |
| Type of result | boolean |
boolean |
boolean |
int |
Treats 'A' and 'a' as |
— | different | same | different |
| Correct for testing equality of text | no | yes | yes, when case is to be ignored | yes, by testing for 0 |
| Usable for sorting | no | no | no | yes |
7.4.6 Searching Inside a String
Four methods answer “where is it?”, and two answer the simpler “does it start or end with it?”.
| Method call | Returns |
|---|---|
s.indexOf(ch) |
index of the first occurrence of the character ch, or −1 |
s.indexOf(ch, from) |
index of the first occurrence at or after index from, or −1 |
s.indexOf(t) |
index where the string t first occurs, or −1 |
s.indexOf(t, from) |
index where t first occurs at or after index from, or −1 |
s.lastIndexOf(ch) |
index of the last occurrence of ch, or −1 |
s.lastIndexOf(t) |
index where t last occurs, or −1 |
s.startsWith(t) |
true if the string begins with t |
s.endsWith(t) |
true if the string ends with t |
Every searching method returns −1 when the thing being looked for is not there. That value is not an index and can never be mistaken for one, which is what makes it a safe answer to “nowhere”.
String s = "Computer Science";
System.out.println(s.indexOf('c'));
System.out.println(s.lastIndexOf('c'));
System.out.println(s.indexOf("Science"));
System.out.println(s.indexOf('z'));prints
10
14
9
-1
Note the first two lines carefully. The capital C at index 0 is not found by a search for the small c, because searching is case sensitive like everything else here.
The version taking a starting index is the important one, because it turns a single search into a repeated one. Search from 0, note where you found it, then search again from just after that position. Repeat until the answer is −1. Fig. 7.4-2 shows the idea on a short sentence.
Diagram
Fig. 7.4-2 – the sentence “the bus to the fort leaves the stand” written along a line with index numbers 0, 11 and 27 marked beneath the three places where “the” begins; three curved arrows drawn above the line, the first from the left end to position 0 labelled “search from 0”, the second from position 1 to position 11 labelled “search from 1”, the third from position 12 to position 27 labelled “search from 12”; a fourth arrow from position 28 running off the right end labelled “search from 28 gives −1”
| Turn | pos on entering the loop |
count after step 4 |
pos after searching from pos + 1 |
|---|---|---|---|
| 1 | 0 | 1 | 11 |
| 2 | 11 | 2 | 27 |
| 3 | 27 | 3 | −1 |
Result: the loop ends with count equal to 3 and the program prints 3. The trace shows the search position moving strictly rightwards, which is the reason the loop is guaranteed to finish.
7.4.7 Replacing Characters and Substrings
The method replace() returns a new string in which every occurrence of one thing has been swapped for another. It comes in two forms: one that replaces a character, and one that replaces a piece of text.
String s = "MXLXYXLXM";
System.out.println(s.replace('X', 'A'));
String t = "the green bottle in the green bag";
System.out.println(t.replace("green", "red"));prints
MALAYALAM
the red bottle in the red bag
Two points, both easy to miss. Replacement happens at every occurrence, not just the first — there is no version that changes only one. And, as always, the original string is untouched; s still holds "MXLXYXLXM" after that first line has run.
The form taking two pieces of text is available from Java 5.0 onwards, which is the version this book assumes.
Table 7.14 gathers the methods of this section in one place, for use while you write the programs of the sections that follow.
| Method call | Returns | What it gives |
|---|---|---|
s.length() |
int |
number of characters in s |
s.charAt(i) |
char |
the character at index i |
s.substring(b) |
String |
characters from index b to the end |
s.substring(b, e) |
String |
characters from index b up to, but not including, e |
s.concat(t) |
String |
s followed by t |
s.toUpperCase() |
String |
s with letters in capitals |
s.toLowerCase() |
String |
s with letters in small case |
s.trim() |
String |
s without leading and trailing whitespace |
s.equals(t) |
boolean |
whether the characters are identical |
s.equalsIgnoreCase(t) |
boolean |
whether they are identical ignoring case |
s.compareTo(t) |
int |
0, negative or positive, giving dictionary order |
s.indexOf(x) |
int |
first index of x, or −1 |
s.indexOf(x, from) |
int |
first index of x at or after from, or −1 |
s.lastIndexOf(x) |
int |
last index of x, or −1 |
s.startsWith(t) |
boolean |
whether s begins with t |
s.endsWith(t) |
boolean |
whether s ends with t |
s.replace(a, b) |
String |
s with every a replaced by b |
7.5 Algorithms on Strings
You now have the whole toolkit: a way to reach any character, a way to cut out a run of them, a way to join, compare, search and replace. What you do not yet have is a way of thinking about string problems, and that is what this section is for.
Almost every string problem set at this level is one of three patterns, or a combination of them. Learn the three patterns and you will rarely have to invent an approach from nothing.
7.5.1 Scanning a String Character by Character
The first pattern examines every character in turn and keeps a running answer. Counting problems, testing problems and searching problems are all of this shape.
The loop is always the same:
for (int i = 0; i < s.length(); i++)
{
char ch = s.charAt(i);
}What changes from problem to problem is only what happens to ch inside the loop.
Consider counting vowels and consonants in a line of text. Two decisions have to be made about each character: first, is it a letter at all, and second, if it is, is it one of the five vowels? Punctuation, digits and blanks belong in neither count and must be passed over.
For the second test there is a neat trick worth learning. Put the five vowels in a string and ask whether the character occurs in it:
if ("aeiou".indexOf(ch) >= 0)Since indexOf() returns −1 when the character is absent, a result of 0 or more means “yes, it is one of these”. This is indexOf() used not to find a position but to test membership, and it saves writing five comparisons joined by ||.
Converting the character to small case first means the test has to list only five vowels instead of ten.
| i | character taken | ch after conversion |
is it a letter? | vowels | consonants |
|---|---|---|---|---|---|
| 0 | 'R' |
'r' |
yes | 0 | 1 |
| 1 | 'a' |
'a' |
yes | 1 | 1 |
| 2 | 'm' |
'm' |
yes | 1 | 2 |
| 3 | '!' |
'!' |
no | 1 | 2 |
Result: the program prints 1 vowel and 2 consonants. The last row is the one to notice — a character that fails the letter test leaves both counts exactly as they were.
7.5.2 Building a New String from an Old One
The second pattern does not count anything. It walks through the old string and assembles a new one, deciding at each step what to add.
The shape is always this:
String result = "";
for (int i = 0; i < s.length(); i++)
{
result = result + s.charAt(i);
}with the order of the walk, or the condition on what gets added, changed to suit the problem. Fig. 7.5-1 shows the two strings involved: one being read, one growing.
Diagram
Fig. 7.5-1 – two rows of boxes; the upper row is the source string with an arrow beneath it labelled “index i moves left to right”, the lower row is the result string shown shorter, with a dashed box at its right end labelled “character added here”; between the rows draw a curved arrow from the box at index i to the dashed box, labelled “add if the condition is satisfied”
Reversing a string is the simplest use. Walk the source from its last index down to 0, adding each character to the result. The result then holds the characters in the opposite order.
A word that reads the same in both directions is a palindrome, and once a string can be reversed the test is one comparison.
7.5.3 Working with the Words of a Sentence
The third pattern treats a sentence not as characters but as words. The words are separated by blanks, so the method is to collect characters until a blank arrives, then deal with the word that has been collected and start an empty one.
Two details make the difference between a program that works and one that nearly works.
The first is the last word. A sentence usually ends with a letter, not a blank, so a program that acts only when it meets a blank will collect the final word and never process it. The cure is to add one blank to the end of the sentence before the scan begins, so that every word — including the last — is followed by one.
The second is repeated blanks. If a sentence contains two spaces between words, the second blank arrives when the collected word is still empty. Processing it would produce a word of no characters, so the program must check that a word has actually been collected before dealing with it.
Fig. 7.5-2 shows the collecting variable filling and emptying as the scan moves along.
Diagram
Fig. 7.5-2 – the sentence “Go now” written as a row of seven boxes with indexes 0 to 6 below them; beneath the row, seven snapshots of a small box labelled “word”, showing in turn G, Go, empty, n, no, now, empty, each snapshot linked by a dotted line to the index that produced it; mark the two blank positions with the label “word is complete: use it, then empty it”
| i | ch |
action taken | word after the action |
|---|---|---|---|
| 0 | 'G' |
added to word | "G" |
| 1 | 'o' |
added to word | "Go" |
| 2 | ' ' |
printed Go (2), emptied word |
"" |
| 3 | 'n' |
added to word | "n" |
| 4 | 'o' |
added to word | "no" |
| 5 | 'w' |
added to word | "now" |
| 6 | ' ' |
printed now (3), emptied word |
"" |
Result: the program prints two lines. The trace shows that the seventh character, the blank added by the program itself, is what causes the last word to be printed at all.
7.6 Conversion Between Strings and Primitive Types
Section 7.1 began with a clerk typing 450 at a fee counter, and with the awkward fact that a program cannot multiply text. It is time to settle that.
Everything that arrives from a keyboard, a file or a code printed on a book arrives as characters. Everything that is displayed leaves as characters too. Numbers exist only in between. So a program spends a surprising amount of its life turning text into numbers and numbers back into text, and Java puts both jobs in the wrapper classes of section 7.1.
Fig. 7.6-1 shows the two directions and the methods that travel them.
Diagram
Fig. 7.6-1 – two boxes side by side, the left one labelled String "450" drawn as three character boxes 4, 5, 0, the right one labelled int 450 drawn as a single value box; an upper arrow from left to right labelled Integer.parseInt, and a lower arrow from right to left labelled Integer.toString and String.valueOf; under the left box write “characters, can be joined and searched”, under the right box write “a quantity, can be added and multiplied”
7.6.1 From a String to a Primitive Value
Reading a piece of text and working out the value it stands for is called parsing. Each numeric wrapper class carries a method that parses text into its own primitive type.
| Method call | Returns | Example |
|---|---|---|
Integer.parseInt(s) |
int |
Integer.parseInt("24") gives 24 |
Long.parseLong(s) |
long |
Long.parseLong("2465783") gives 2465783 |
Float.parseFloat(s) |
float |
Float.parseFloat("24.35") gives 24.35 |
Double.parseDouble(s) |
double |
Double.parseDouble("24.5") gives 24.5 |
Each is called on the class, not on an object, so the class name is written in front of it. Each takes a String and hands back a plain value you can calculate with.
There is a second family of methods with a similar purpose and a different result. Integer.valueOf(s) also reads the text, but it returns an Integer object rather than an int.
| Feature | Integer.parseInt(s) |
Integer.valueOf(s) |
|---|---|---|
| What it returns | an int, a primitive value |
an Integer, an object |
| Ready for arithmetic | yes, immediately | only after unboxing |
| Usable where an object is required | no | yes |
| Behaviour when the text is not a number | throws NumberFormatException |
throws NumberFormatException |
From Java 5.0 onwards the difference is easy to overlook, because unboxing will quietly turn the object into a value on assignment, so int n = Integer.valueOf("24"); compiles and works. Overlooking it is still a mistake: the object was built and then thrown away. When you want a number to calculate with, parseInt() says so plainly.
A common use is a value buried inside a longer piece of text, where the digits must first be cut out with substring() and only then parsed.
7.6.2 From a Primitive Value to a String
The journey back is easier, because Java is generous about it. Three ways are available, and all three give the same result.
| Way of writing it | Example | Result |
|---|---|---|
Integer.toString(n) |
Integer.toString(45) |
"45" |
String.valueOf(n) |
String.valueOf(45) |
"45" |
| joining to an empty string | "" + 45 |
"45" |
Long.toString(), Float.toString() and Double.toString() do the same for the other numeric types, while String.valueOf() accepts a value of any type at all, which is why it is often the shortest thing to write.
The third way explains something you have been using since the first program of this chapter. When System.out.println("Price : " + price) runs, the number on the right of the + is converted to text automatically, because the value on the left is a string. You have been converting numbers all along without noticing.
Why convert deliberately, then? Because once a number is text, the whole toolkit of section 7.4 becomes available to it. Counting the digits of a number, for instance, needs no arithmetic at all:
int n = 45678;
String s = Integer.toString(n);
System.out.println("The number has " + s.length() + " digits");prints
The number has 5 digits
Testing whether a number reads the same backwards, or checking whether a particular digit occurs in it, becomes just as easy — a string question rather than a division-and-remainder question.
7.6.3 When the String Is Not a Number
Parsing can fail, and it fails loudly. If the text does not spell a proper number, the parsing method throws a NumberFormatException and the program stops at that line.
All of these fail:
Integer.parseInt("twelve");
Integer.parseInt("12a");
Integer.parseInt("45.7");
Integer.parseInt(" 45");
Integer.parseInt("");The last two deserve comment. "45.7" is a perfectly good number but not a whole one, so parseInt() refuses it while Double.parseDouble() accepts it happily. And " 45" fails on nothing worse than a blank space, which is exactly why input from a keyboard is passed through trim() before it is parsed.
A program that stops with an exception in front of a user has not handled the situation; it has merely discovered it. The better habit is to check the text first and parse only when the check passes.
Checking input before trusting it is not extra work. It is the difference between a program that behaves predictably for every input and one that behaves well only for the inputs its author happened to try.
7.7 The StringBuffer Class
Twice in this chapter a cost has been noted and postponed. In section 7.3.3 you saw that joining strings makes a new object every time, and in section 7.5.2 the reversal algorithm carried a cost of \(O(n^2)\) for that reason alone.
This section pays the debt. Java provides a second class for holding text, built on the opposite principle, and knowing when to reach for it is part of writing programs that scale.
7.7.1 The Cost of Repeated String Building
Look once more at the line that does the damage:
result = result + s.charAt(i);Because a String cannot be altered, this statement cannot add a character to result. It must build a new object holding everything result had plus one more character, point result at it, and abandon the old object. On the next turn it does the same thing again, with one more character to copy.
Building 10 characters costs about 55 copies, which nobody notices. Building 100,000 characters costs about five thousand million copies, which everybody notices. The work grows as the square of the length, while the text grows only in proportion to it.
The cure is an object that can be altered. Java calls it a StringBuffer.
An object whose contents can be changed after it has been created is said to be mutable, the opposite of the immutability studied in section 7.3.3. A StringBuffer also keeps spare room at the end of its store, so that adding characters usually needs no copying at all. Fig. 7.7-1 contrasts the two ways of growing text.
Diagram
Fig. 7.7-1 – two panels; the upper panel shows four separate string objects in a row containing J, Ja, Jai and Jaip, with the first three crossed out and labelled “abandoned”, and an arrow from the last labelled “the variable now points here”; the lower panel shows one buffer drawn as a long row of cells with J, a, i, p in the first four and the rest empty, an arrow pointing at the fifth cell labelled “the next character goes here”, and the whole panel labelled “one object, altered in place”
7.7.2 Creating a StringBuffer Object
A StringBuffer has no literal form. There is no way to write one between quotes, so every one of them is made with new.
Three constructors are used:
StringBuffer a = new StringBuffer();
StringBuffer b = new StringBuffer(40);
StringBuffer c = new StringBuffer("Jaipur");The first creates an empty buffer with room for 16 characters. The second creates an empty buffer with room for as many characters as you ask for. The third creates a buffer already holding the given text, with 16 characters of spare room beyond it.
Two different measurements of a buffer follow from this, and they are easy to confuse. The length is the number of characters actually held. The capacity is the number the buffer has room for before it must be enlarged. Fig. 7.7-2 shows both for the third buffer above.
Diagram
Fig. 7.7-2 – a long row of 22 cells; the first six contain J, a, i, p, u, r and are shaded, the remaining sixteen are empty; a bracket under the first six labelled “length = 6”, a bracket under the whole row labelled “capacity = 22”, and a note beside the empty cells reading “spare room: 16 characters”
7.7.3 Operations on a StringBuffer
The methods of StringBuffer do what their names say, and — this is the whole point of the class — they do it to the object they are called on.
| Method call | Effect on the buffer b |
|---|---|
b.append(x) |
adds x at the end; x may be text, a number or a character |
b.insert(i, x) |
inserts x so that it begins at index i |
b.delete(i, j) |
removes the characters from index i up to, but not including, j |
b.setCharAt(i, ch) |
replaces the character at index i by ch |
b.reverse() |
reverses the order of all the characters |
b.setLength(n) |
makes the length exactly n, cutting or padding as needed |
b.charAt(i) |
returns the character at index i; the buffer is unchanged |
b.length() |
returns the number of characters held |
b.capacity() |
returns the room currently available |
b.toString() |
returns the contents as a String; the buffer is unchanged |
Three short examples show the difference from String most clearly. In each, the buffer itself changes and no result has to be caught:
StringBuffer s = new StringBuffer("COMMUTER");
s.setCharAt(3, 'P');
System.out.println(s);
StringBuffer t = new StringBuffer("COMPUTER");
t.delete(3, 5);
System.out.println(t);
StringBuffer u = new StringBuffer("STRESSED");
u.reverse();
System.out.println(u);prints
COMPUTER
COMTER
DESSERTS
The delete() call removes the characters at indexes 3 and 4 — P and U — because the second index marks where the removal stops, exactly as it does in substring().
Now put the class to its proper use: building text a piece at a time.
| i | after append(sales[i]) |
separator added? | contents of line |
|---|---|---|---|
| 0 | 120 |
yes | 120, |
| 1 | 85 |
yes | 120, 85, |
| 2 | 200 |
yes | 120, 85, 200, |
| 3 | 45 |
no | 120, 85, 200, 45 |
Result: after the loop the buffer holds 120, 85, 200, 45; insert() then puts the heading in front of it, giving a line of 23 characters. Notice that the same buffer appears in every row — there is only ever one object, growing.
7.7.4 Choosing Between String and StringBuffer
Both classes hold text and both offer methods for working with it, so the choice can look arbitrary. It is not.
| Feature | String |
StringBuffer |
|---|---|---|
| Contents after creation | can never change | can be altered in place |
| Created by a quoted literal | yes | no, always with new |
What a method such as toUpperCase() or append() gives back |
a new object; the original is untouched | the same object, now altered |
| Cost of building \(n\) characters piece by piece | \(O(n^2)\) | \(O(n)\) |
| Spare room kept in advance | none | yes, reported by capacity() |
| Safe to share between parts of a program | yes, since nobody can alter it | no, since anyone holding it can alter it |
| Suited to | text that is read, compared and searched | text that is assembled or edited |
The last two rows are the honest trade. Immutability is not a defect that StringBuffer repairs; it is a guarantee that StringBuffer gives up in exchange for speed. A String can be handed to any part of a program with no fear that it will come back changed. A StringBuffer carries no such promise.
So the working rule is simple. Use String for text you are examining, and StringBuffer for text you are building or editing — especially inside a loop. When the building is finished, call toString() once and go back to the safety of a String.
7.8 The StringTokenizer Class
In section 7.5.3 you wrote out, by hand, the business of collecting characters until a blank arrives. It worked, but it needed three careful decisions — the added blank at the end, the emptying of the collecting variable, the guard against repeated blanks — and every one of them is a place to go wrong.
Splitting text into its parts is such a common job that Java supplies a class for it. This section is about that class, and about the vocabulary that goes with it.
7.8.1 Tokens, Whitespace and Delimiters
Start with the two words that matter.
A token is one meaningful piece of a larger piece of text — usually a word, a number or a code. A delimiter is a character that separates one token from the next and is not part of either.
In an ordinary sentence the delimiters are the whitespace characters you met in section 7.2.2 — the blank space, the tab and the newline. In a stored record they are often something else: a colon, a comma or a hyphen chosen by whoever designed the record.
Fig. 7.8-1 shows the same idea for both cases.
Diagram
Fig. 7.8-1 – two rows; the upper row shows the sentence “Reading improves the mind” with the three blank positions marked by small crosses labelled “delimiters, discarded” and the four words boxed and labelled “tokens”; the lower row shows the record “Idli:30:2” with the two colons marked by crosses labelled “delimiters, discarded” and the three parts boxed and labelled “tokens”
Notice what happens to the delimiters. They do their job of marking a boundary and are then thrown away; they never appear in any token. That is why splitting a sentence into words loses the spaces between them, and why a program that must rebuild the sentence has to put separators back itself.
The class that performs the splitting is StringTokenizer, and it lives in the package java.util. Unlike the wrapper classes and String, it is not imported automatically, so a program that uses it must begin with an import statement:
import java.util.StringTokenizer;7.8.2 Creating a StringTokenizer Object
A tokenizer is created with new, and is given the text it has to break up.
StringTokenizer st = new StringTokenizer("Reading improves the mind");With one argument, the delimiters are the whitespace characters: the blank space, the tab, the newline, the carriage return and the form feed. A run of several of them counts as one boundary, which is why repeated blanks give no trouble here — the very problem that needed a guard in section 7.5.3.
With two arguments, you choose the delimiters yourself by listing them in a second string.
StringTokenizer st = new StringTokenizer("rice:wheat:sugar", ":");Every character in that second string is treated as a delimiter in its own right, so ", " means “a comma or a blank”, not “a comma followed by a blank”.
The object created this way is not a copy of the text and not a list of words. It is best pictured as a queue of tokens waiting to be handed out, one at a time, as Fig. 7.8-2 shows.
Diagram
Fig. 7.8-2 – a rectangle labelled “StringTokenizer” containing four boxes in a row reading Reading, improves, the, mind; an arrow leaving the left end labelled “nextToken() takes the front one”; below, three snapshots of the same rectangle holding first three boxes, then two, then none, each labelled with the value countTokens() would return: 3, 2, 0
7.8.3 Extracting Tokens One by One
Three methods do all the work.
| Method call | Returns | What it does |
|---|---|---|
st.countTokens() |
int |
how many tokens are still waiting |
st.hasMoreTokens() |
boolean |
whether at least one token is still waiting |
st.nextToken() |
String |
the next token, which is then removed from the queue |
The middle column of that table hides the one fact students most often miss. countTokens() reports the tokens that remain, not the tokens the text originally had. Take three of them out and it will answer with whatever is left.
The standard loop follows directly from the other two methods:
while (st.hasMoreTokens())
{
String word = st.nextToken();
}Ask whether anything is left; if so, take it. The loop ends by itself when the queue is empty, so no counting is needed and no word can be missed.
Tokens are just as useful for stored records as for sentences. A canteen keeps each line of its price list as an item name, a rate and a quantity, joined by colons, and a program has to take that line apart before it can calculate anything.
7.8.4 Solving Sentence Problems with Tokens
Most sentence problems set at this level have the same shape: take the words one at a time, ask something about each, and keep a running answer. Finding the longest word is the plainest example of the pattern.
The counting in step 2 has to be done before the loop, not after it. Once the loop has emptied the queue, there is nothing left to count.
| Token taken | its length | length of longest before |
is it longer? | longest after |
|---|---|---|---|---|
Go |
2 | 0 | yes | Go |
to |
2 | 2 | no | Go |
the |
3 | 2 | yes | the |
fort |
4 | 3 | yes | fort |
Result: words was set to 4 before the loop began, and the loop ends with longest holding fort. The second row shows the tie-breaking rule at work: to is exactly as long as Go and is therefore not accepted.
| Feature | Hand-written scan with charAt() |
StringTokenizer |
|---|---|---|
| What the loop handles | one character at a time | one whole word at a time |
| Repeated blanks between words | must be guarded against | treated as one boundary |
| The last word of the sentence | needs a blank added, or handling after the loop | needs nothing special |
| Choice of separator | any test you care to write | any set of single characters you list |
| Number of words known in advance | only by counting yourself | from countTokens() |
| Access to individual characters | direct | only after taking the token |
Neither approach is the better one in general. When a problem is about whole words — counting them, ordering them, comparing them — the tokenizer removes most of the ways of going wrong. When a problem is about the characters inside the words, you will be scanning with charAt() in any case, and often the neatest programs do both: a tokenizer to reach each word, and a character scan inside it.
7.9 Summary
Wrapper classes
A primitive type is built into the language and a variable of such a type holds one plain value, with no methods and no constants attached to it.
A wrapper class is a class whose object holds a single value of one primitive type and supplies the methods and constants that belong to that type. There is exactly one wrapper class for each of the eight primitive types:
Byteforbyte,Integerforint,Shortforshort,Longforlong,Floatforfloat,Doublefordouble,BooleanforbooleanandCharacterforchar.All eight belong to
java.langand need no import. The six numeric wrappers are grouped under the classNumber;CharacterandBooleanare not.Autoboxing is the automatic conversion of a primitive value into an object of its wrapper class; unboxing is the automatic conversion of a wrapper object back into the primitive value it holds. Both are performed by the compiler from Java 5.0 onwards.
A wrapper object costs more memory and more time than a plain value, so the primitive type remains the right choice for ordinary arithmetic.
Characters and their codes
Every character is stored as a number. Java uses Unicode, whose first 128 codes are those of ASCII — the American Standard Code for Information Interchange.
The codes to remember are 32 for the blank space, 48 to 57 for the digits, 65 to 90 for the capital letters and 97 to 122 for the small letters. Each group is unbroken, and the same letter in the two cases differs by exactly 32.
A digit character is not the number it shows. The value of a digit character is obtained by subtracting the code of
'0'from it.Whitespace is any character that occupies space without printing a mark — the blank space, the tab and the newline. It is what separates one word from the next.
The
Characterclass supplies methods that test a character (whether it is a letter, a digit, a letter or digit, a capital, a small letter or whitespace) and methods that convert one (to capital, to small, or to a one-character string). Arithmetic on a character uses its code, so turning a code back into a character needs a type cast.
The String class
A
Stringobject holds a fixed sequence of characters, numbered from 0 up to one less than the length. The last index is therefore always one less than the length, and a loop over a string must stop before the index reaches the length.A string may be created from a literal or with
new. Identical literals share one stored object, whilenewalways builds a fresh one — which is why two strings must never be compared with==.A
Stringis immutable: its contents can never be altered after it has been created. Every method that appears to change a string in fact returns a new string and leaves the original untouched, so the result has to be caught in a variable.The methods of the class answer six kinds of question: how long the string is and what character stands at an index; what run of characters lies between two positions; how two strings are joined; how they compare; where something occurs inside a string; and what the string looks like with one thing replaced by another.
A substring is taken between two cut positions: the character at the beginning index is included and the character at the ending index is not, so the length of the result is the ending index minus the beginning index.
Equality of contents is tested with
equals(), or withequalsIgnoreCase()when case is to be ignored. Dictionary order is given bycompareTo(), which returns 0 for identical strings, a negative number when the first comes earlier and a positive number when it comes later; the value returned is the difference of the first pair of differing codes, or the difference of the lengths when one string is a prefix of the other.Every searching method returns −1 when what is sought is not present, and that value can never be mistaken for an index.
Algorithms on strings
Three patterns cover almost every string problem at this level: scanning the characters one at a time and keeping a running answer; building a new string by adding characters conditionally; and taking a sentence word by word.
A membership test can be written by putting the acceptable characters in a string and asking whether the character occurs in it, since a result of 0 or more means “present”.
Scanning a string of \(n\) characters costs \(O(n)\), where \(n\) counts the characters. Building a string of \(n\) characters by repeated joining costs \(O(n^2)\), because each join copies everything assembled so far.
A hand-written word scan must add a separator at the end so that the last word is not lost, must empty the collecting variable after each word, and must ignore empty words produced by repeated blanks.
Converting between text and numbers
Parsing is reading a piece of text and working out the value it stands for. Each numeric wrapper class supplies a parsing method that returns a primitive value, while its
valueOf()method returns a wrapper object instead.A number becomes text through the
toString()method of its wrapper class, throughString.valueOf(), or simply by being joined to a string — which is what happens in every display statement that joins a heading to a value.Parsing fails with a
NumberFormatExceptionwhen the text does not spell a proper number of the required kind, including when it carries a stray blank or a decimal point that a whole number cannot have. Checking the text before parsing it is part of writing a program that behaves predictably for every input.
The StringBuffer class
A
StringBufferobject holds characters that can be altered in place. Such an object is mutable — the opposite of aString.A buffer has both a length, the number of characters it holds, and a capacity, the number it has room for. An empty buffer is given room for 16 characters by default, and a buffer created from text is given 16 characters beyond the length of that text.
Its methods alter the object they are called on rather than returning a new one, and they allow characters to be added at the end, inserted at a position, removed between two positions, replaced singly, reversed, or cut and padded to a fixed length.
Building \(n\) characters in a buffer costs \(O(n)\) against \(O(n^2)\) for the same job done by joining strings, where \(n\) counts the characters in the finished text. The price of that speed is the loss of the guarantee that shared text cannot be altered behind your back.
The working rule is to use a
Stringfor text that is examined and aStringBufferfor text that is assembled or edited, converting back to aStringonce the building is over.
The StringTokenizer class
A token is a run of characters treated as one unit; a delimiter is a character that marks the boundary between tokens and is not part of either. Delimiters are discarded during splitting.
A
StringTokenizer, from the packagejava.util, breaks text into tokens. Created with the text alone it uses whitespace as its delimiters, treating a run of blanks as a single boundary; created with a second string it uses each character of that string as a delimiter.The object behaves as a queue of tokens waiting to be taken. It reports whether any remain, hands out the next one, and counts those still waiting — so the count must be taken before the tokens are consumed, and a tokenizer cannot be rewound.
Splitting a sentence of \(n\) characters costs \(O(n)\), where \(n\) counts the characters. Using a tokenizer removes the three awkward details of a hand-written scan, but the characters inside a token are still reached by scanning that token.
7.10 Key Terms
ASCII — American Standard Code for Information Interchange; the agreed table fixing a numeric code for each letter, digit, punctuation mark and control character, and the source of the first 128 codes of Unicode.
autoboxing — the automatic conversion, performed by the compiler, of a primitive value into an object of its corresponding wrapper class.
capacity — the number of characters a
StringBufferhas room for before it must be enlarged, which is never less than its length.concatenation — the joining of two pieces of text end to end to form a longer one, written with the
+operator or withconcat().delimiter — a character that marks the boundary between one token and the next, and that forms no part of either token.
immutable — describing an object whose contents cannot be altered after it has been created, so that every operation on it produces a new object; a
Stringis immutable.length — the number of characters actually held, reported by
length()for aStringand for aStringBuffer.mutable — describing an object whose contents can be altered in place after it has been created; a
StringBufferis mutable.palindrome — a word or sequence of characters that reads the same from either end.
parsing — reading a piece of text and working out the value it stands for, as
Integer.parseInt()does for a whole number.primitive type — a type built into the language itself, a variable of which holds one plain value and carries no methods; the eight are
byte,int,short,long,float,double,booleanandchar.string literal — text written between double quotes in a program, which is itself a
Stringobject.substring — any run of consecutive characters taken from a string.
token — a run of consecutive characters treated as one unit, such as a word, a number or a code within a larger piece of text.
type cast — a conversion written by the programmer, in brackets before a value, instructing the compiler to treat that value as being of another type.
unboxing — the automatic conversion, performed by the compiler, of a wrapper object into the primitive value it holds.
Unicode — the character code used by Java, designed to cover the writing systems of the world, whose first 128 codes are those of ASCII.
whitespace — any character that occupies space on a line without printing a mark of its own, namely the blank space, the tab and the newline.
wrapper class — a class whose object holds a single value of one primitive type and supplies the methods and constants associated with that type.
7.11 Points to Ponder
Immutability forces a copy every time text is changed, and the whole of section 7.7 exists to work around that cost. Why might a language designer still consider immutability worth the price? What could go wrong in a program in which any part could quietly alter a string that another part was relying on?
Java could have offered only one class for text — a mutable one — and left programmers to be careful. Instead it offers two, and expects you to choose. Is a language that gives you a choice with a cost attached better than one that decides for you? Where else in programming have you met the same trade?
Because capital letters were given smaller codes than small ones,
"Zebra"comes before"apple"in Java’s ordering, which no dictionary would accept. Should the ordering of a language follow the codes, which are simple and predictable, or the conventions of a written language, which differ from one language to another?The searching methods answer “nowhere” by returning −1, a value that could never be a real index. Suppose the library had chosen to return 0 instead, or to stop the program with an exception. What would each of those choices have made easier, and what would it have made harder?
A digit character and the number it shows are stored as different values, and converting between them takes deliberate work. Would anything be gained if a language stored
'7'and7identically? Think about what would then happen to a program that has to decide whether the user typed a number or a word.A
StringTokenizerthrows its delimiters away, so a sentence split into words cannot be put back together exactly as it was. When would a program need a splitting tool that kept the separators, and what would such a tool have to hand back that this one does not?
7.12 Objective Questions
- A string holds the word ENVIRONMENT. What is the index of its last character?
- 9
- 10
- 11
- 12 [1 mark]
- Which method reports how many tokens are still waiting in a
StringTokenizerobject?
length()size()countTokens()hasMoreTokens()[1 mark]
- The wrapper class corresponding to the primitive type
charis:
CharChrCharacterCharValue[1 mark]
- What is the value of
"Computer".substring(2, 5)?
"mpu""mput""ompu""put"[1 mark]
- Which of the following returns a primitive
intrather than an object?
Integer.valueOf("24")Integer.parseInt("24")Integer.toString(24)String.valueOf(24)[1 mark]
- Assertion (A): A
Stringobject cannot be altered after it has been created. Reason (R): Every method of theStringclass returns a new object and leaves the original untouched. Choose the correct option:
- Both (A) and (R) are true and (R) is the correct explanation of (A).
- Both (A) and (R) are true but (R) is not the correct explanation of (A).
- is true but (R) is false.
- is false but (R) is true. [1 mark]
- Assertion (A): Two strings holding the same characters may compare as unequal when tested with
==. Reason (R): The==operator tests whether two variables refer to the same object, not whether their contents match. Choose the correct option:
- Both (A) and (R) are true and (R) is the correct explanation of (A).
- Both (A) and (R) are true but (R) is not the correct explanation of (A).
- is true but (R) is false.
- is false but (R) is true. [1 mark]
- Match each method call in Column I with what it hands back, from Column II.
| Column I | Column II |
|---|---|
a. s.charAt(2) |
i. a boolean |
b. s.compareTo(t) |
ii. a String |
c. s.endsWith(t) |
iii. a char |
d. b.toString(), where b is a StringBuffer |
iv. an int |
[1 mark]
Fill in the blank: the automatic conversion of a primitive value into an object of its wrapper class is called ____________. [1 mark]
State whether true or false: the
trim()method removes the blank spaces occurring between the words of a string. [1 mark]Write the full form of ASCII. [1 mark]
7.13 Check Your Concepts
Define a wrapper class, and name the wrapper classes corresponding to the primitive types
charandint. [2 marks]What is meant by whitespace? Name the three whitespace characters used in this chapter. [2 marks]
Define a token and a delimiter, giving one example of each from a record of the form
Idli:30:2. [2 marks]What value does a searching method of the
Stringclass return when the character or substring sought is not present? Explain why that particular value was chosen. [2 marks]Differentiate between
equals()andcompareTo()on three grounds: the question each asks, the type of value each returns, and the use to which the result is put. [3 marks]Differentiate between autoboxing and unboxing, giving one statement of Java to illustrate each. [3 marks]
A student writes
title.toUpperCase();and finds that the string held intitleis unchanged when printed. Give reasons for this behaviour and state the correction needed. [3 marks]Explain, with a suitable example, why
s.substring(b, e)returns exactlye − bcharacters. State whats.substring(4, 4)returns and why this is not an error. [4 marks]Give the cost, in big O notation, of building a text of n characters (i) by repeatedly joining strings and (ii) by appending to a
StringBuffer. Justify each answer by saying what work is done at each step, and state clearly what n counts. [4 marks]Differentiate between
StringandStringBufferunder at least four heads. Then state, with reasons, which of the two you would use for (i) a student’s name that is read once and compared many times, and (ii) a report line assembled from fifty separate pieces. [6 marks]Write an algorithm, in pseudocode, that displays each word of a sentence without using the
StringTokenizerclass. Name the three situations your algorithm must handle correctly — the last word, repeated blanks, and blanks at the ends of the sentence — and show how each is handled. State the cost of the algorithm in big O notation, saying what n counts. [6 marks]
7.14 Output & Debugging
- Predict the output of the following fragment.
String s = "Rajasthan";
System.out.println(s.indexOf('a'));
System.out.println(s.lastIndexOf('a'));
System.out.println(s.substring(4, 7));[2 marks]
- State what each of the three statements prints, and explain in one line why the first two differ.
String a = "40";
int b = 5;
System.out.println(a + b);
System.out.println(Integer.parseInt(a) + b);
System.out.println("Sum: " + b + b);[3 marks]
- Give the output of the following fragment, and say in one line what it computes in general.
String word = "SCHOOL";
String r = "";
for (int i = 0; i < word.length(); i++)
{
if (word.charAt(i) != 'O')
{
r = r + word.charAt(i);
}
}
System.out.println(r);
System.out.println(r.length());[3 marks]
- The following fragment prints seven characters and then stops with an error. Name the error, state exactly when it occurs, and rewrite the loop correctly.
String city = "Udaipur";
for (int i = 0; i <= city.length(); i++)
{
System.out.print(city.charAt(i));
}[3 marks]
- Trace the following fragment and give the two lines it prints.
StringBuffer b = new StringBuffer("PLANT");
b.setCharAt(2, 'I');
b.delete(3, 5);
b.append("CE");
System.out.println(b);
System.out.println(b.length());[3 marks]
- Give the complete output of this program. The count printed on the last line is not what its author expected: state the value printed, explain why, and correct the program with the smallest change you can.
import java.util.StringTokenizer;
class Count
{
public static void main(String args[])
{
StringTokenizer st = new StringTokenizer("rain in the plains");
while (st.hasMoreTokens())
{
System.out.println(st.nextToken());
}
System.out.println("Words = " + st.countTokens());
}
}[4 marks]
7.15 Programming Practice
Write a method
int vowels(String w)that accepts a word and returns the number of vowels in it, counting capital and small letters alike. Submit the method only, together with a variable description table for the variables it uses. [5 marks]Design a class to accept a sentence from the keyboard and display it with the first letter of every word converted to a capital and the remaining letters left as typed. The words may be separated by more than one blank space. Sample input:
the red fort at agraSample output:The Red Fort At AgraSubmit an algorithm, the complete class and a variable description table. [6 marks]Design a class to accept a sentence terminated by a full stop and display every word that begins and ends with the same letter, ignoring the difference between capital and small letters, followed by the number of such words. The full stop is not part of any word. Sample input:
Level ground near the test area.Sample output should list the qualifying words, one to a line, and then their count. Submit an algorithm, the complete class and a variable description table. [10 marks]Design a class to accept a whole number of at most nine digits typed as text. If every character typed is a digit, display the sum of the digits and state whether the number reads the same from either end; otherwise display a message saying that the input is not a whole number. The program must not stop with an exception on any input. Sample input:
12321Sample output should give the sum of the digits and the verdict on reading it backwards. Submit the complete class and a variable description table. [10 marks]Design a class to accept a sentence and build a single new line in which each word is followed by the number of letters in that word, written in brackets, with one blank space between the entries. The new line must be assembled in a
StringBufferand converted to aStringonly once, at the end. Sample input:Books open doorsSample output:Books(5) open(4) doors(5)Submit the complete class, a variable description table, and one line stating why aStringBufferis the better choice here. [10 marks]