Chapter 11
Using Arrays and Collections to Juggle Values
In This Chapter
Dealing with several values at once
Creating values as you get a program running
Impressing other programmers with fancy generic types
Welcome to the Java Motel! No haughty bellhops, no overpriced room service, none of the usual silly puns. Just a clean double room that’s a darn good value!
Getting Your Ducks All in a Row
The Java Motel, with its ten comfortable rooms, sits in a quiet place off the main highway. Aside from a small, separate office, the motel is just one long row of ground floor rooms. Each room is easily accessible from the spacious front parking lot.
Oddly enough, the motel’s rooms are numbered 0 through 9. I could say that the numbering is a fluke — something to do with the builder’s original design plan. But the truth is that starting with 0 makes the examples in this chapter easier to write.
Anyway, you’re trying to keep track of the number of guests in each room. Because you have ten rooms, you may think about declaring ten variables:
int guestsInRoomNum0, guestsInRoomNum1, guestsInRoomNum2,
guestsInRoomNum3, guestsInRoomNum4, guestsInRoomNum5,
guestsInRoomNum6, guestsInRoomNum7, guestsInRoomNum8,
guestsInRoomNum9;
Doing it this way may seem a bit inefficient — but inefficiency isn’t the only thing wrong with this code. Even more problematic is the fact that you can’t loop through these variables. To read a value for each variable, you have to copy the nextInt
method ten times.
guestsInRoomNum0 = diskScanner.nextInt();
guestsInRoomNum1 = diskScanner.nextInt();
guestsInRoomNum2 = diskScanner.nextInt();
... and so on.
Surely a better way exists.
That better way involves an array. An array is a row of values, like the row of rooms in a one-floor motel. To picture the array, just picture the Java Motel:
First, picture the rooms, lined up next to one another.
Next, picture the same rooms with their front walls missing. Inside each room you can see a certain number of guests.
If you can, forget that the two guests in Room 9 are putting piles of bills into a big briefcase. Ignore the fact that the guests in Room 6 haven’t moved away from the TV set in a day and a half. Instead of all these details, just see numbers. In each room, see a number representing the count of guests in that room. (If freeform visualization isn’t your strong point, look at Figure 11-1.)
In the lingo of this chapter, the entire row of rooms is called an array. Each room in the array is called a component of the array (also known as an array element). Each component has two numbers associated with it:
The room number (a number from 0 to 9), which is called an index of the array
A number of guests, which is a value stored in a component of the array
Using an array saves you from all the repetitive nonsense in the sample code shown at the beginning of this section. For instance, to declare an array with ten values in it, you can write one fairly short statement:
int guests[] = new int[10];
If you’re especially verbose, you can expand this statement so that it becomes two separate statements:
int guests[];
guests = new int[10];
In either of these code snippets, notice the use of the number 10. This number tells the computer to make the guests
array have ten components. Each component of the array has a name of its own. The starting component is named guests[0], the next is named guests[1], and so on. The last of the ten components is named guests[9].
Creating an array in two easy steps
Look once again at the two lines that you can use to create an array:
int guests[];
guests = new int[10];
Each line serves its own distinct purpose:
int guests[]
: This first line is a declaration. The declaration reserves the array name (a name like guests) for use in the rest of the program. In the Java Motel metaphor, this line says, “I plan to build a motel here and put a certain number of guests in each room.” (See Figure 11-2.)
Never mind what the declaration int guests[]
actually does. It’s more important to notice what the declaration int guests[]
doesn’t do. The declaration doesn’t reserve ten memory locations. Indeed, a declaration like int guests[]
doesn’t really create an array. All the declaration does is set up the guests
variable. At that point in the code, the guests
variable still doesn’t refer to a real array. (In other words, the motel has a name, but the motel hasn’t been built yet.)
guests = new int[10]
: This second line is an assignment statement. The assignment statement reserves space in the computer’s memory for ten int
values. In terms of real estate, this line says, “I’ve finally built the motel. Go ahead and put guests in each room.” (Again, see Figure 11-2.)
Storing values
After you’ve created an array, you can put values into the array’s components. For instance, you would like to store the fact that Room 6 contains 4 guests. To put the value 4
in the component with index 6
, you write guests[6] = 4.
Now business starts to pick up. A big bus pulls up to the motel. On the side of the bus is a sign that says “Noah’s Ark.” Out of the bus come 25 couples, each walking, stomping, flying, hopping, or slithering to the motel’s small office. Only 10 of the couples can stay at the Java Motel, but that’s okay because you can send the other 15 couples down the road to the old C-Side Resort and Motor Lodge.
Anyway, to register 10 couples into the Java Motel, you put a couple (2 guests) in each of your 10 rooms. Having created an array, you can take advantage of the array’s indexing and write a for
loop, like this:
for (int roomNum = 0; roomNum < 10; roomNum++) {
guests[roomNum] = 2;
}
This loop takes the place of ten assignment statements. Notice how the loop’s counter goes from 0 to 9. Compare this with Figure 11-2, and remember that the indices of an array go from 0 to one less than the number of components in the array.
However, given the way the world works, your guests won’t always arrive in neat pairs, and you’ll have to fill each room with a different number of guests. You probably store information about rooms and guests in a database. If you do, you can still loop through an array, gathering numbers of guests as you go. The code to perform such a task may look like this:
resultset =
statement.executeQuery(“select GUESTS from RoomData”);
for (int roomNum = 0; roomNum < 10; roomNum++) {
resultset.next();
guests[roomNum] = resultset.getInt(“GUESTS”);
}
But because this book doesn’t cover databases until Chapter 16 you may be better off reading numbers of guests from a plain text file. A sample file named GuestList.txt
is shown in Figure 11-3. After you’ve made a file, you can call on the Scanner
class to get values from the file. The code is shown in Listing 11-1, and the resulting output is in Figure 11-4.
Listing 11-1: Filling an Array with Values
import static java.lang.System.out;
import java.util.Scanner;
import java.io.File;
import java.io.IOException;
class ShowGuests {
public static void main(String args[])
throws IOException {
int guests[] = new int[10];
Scanner diskScanner =
new Scanner(new File(“GuestList.txt”));
for(int roomNum = 0; roomNum < 10; roomNum++) {
guests[roomNum]
= diskScanner.nextInt();
}
out.println(“Room Guests”);
for(int roomNum = 0; roomNum < 10; roomNum++) {
out.print(roomNum);
out.print(“ ”);
out.println(
guests[roomNum]
);
}
}
}
The code in Listing 11-1 has two for
loops. The first loop reads numbers of guests, and the second loop writes numbers of guests.
Tab stops and other special things
In Listing 11-1, some calls to print
and println
use the
escape sequence. It’s called an escape sequence because you escape from displaying the letter t
on the screen. Instead, the characters
stand for a tab. The computer moves forward to the next tab stop before printing any more characters. Java has a few of these handy escape sequences. Some of them are shown in Table 11-1.
Table 11-1 Escape Sequences
Sequence |
Meaning |
|
backspace |
|
horizontal tab |
|
line feed |
|
form feed |
|
carriage return |
|
double quote “ |
|
single quote ‘ |
|
backslash |
Using an array initializer
Besides what you see in Listing 11-1, you have another way to fill an array in Java — with an array initializer. When you use an array initializer, you don’t even have to tell the computer how many components the array has. The computer figures this out for you.
Listing 11-2 shows a new version of the code to fill an array. The program’s output is the same as the output of Listing 11-1. (It’s the stuff shown in Figure 11-4.) The only difference between Listings 11-1 and 11-2 is the bold text in Listing 11-2. That bold doodad is an array initializer.
Listing 11-2: Using an Array Initializer
import static java.lang.System.out;
class ShowGuests {
public static void main(String args[]) {
int guests[] =
{1, 4, 2, 0, 2, 1, 4, 3, 0, 2}
;
out.println(“Room Guests”);
for (int roomNum = 0; roomNum < 10; roomNum++) {
out.print(roomNum);
out.print(“ ”);
out.println(guests[roomNum]);
}
}
}
Stepping through an array with the enhanced for loop
Java has an enhanced for
loop — a for
loop that doesn’t use counters or indices. Listing 11-3 shows you how to do it.
Listing 11-3: Get a Load o’ that for Loop!
import static java.lang.System.out;
class ShowGuests {
public static void main(String args[]) {
int guests[] = {1, 4, 2, 0, 2, 1, 4, 3, 0, 2};
int roomNum = 0;
out.println(“Room Guests”);
for (
int numGuests : guests
) {
out.print(roomNum++);
out.print(“ ”);
out.println(numGuests);
}
}
}
Listings 11-1 and 11-3 have the same output. It’s in Figure 11-4.
If you look at the loop in Listing 11-3, you see the same old pattern. Just like the loops in Listing 6-5, this example’s loop has three parts:
for (
variable-type
variable-name
: range-of-values
)
The first two parts are variable-type
and variable-name
. The loop in Listing 11-3 defines a variable named numGuests
, and numGuests
has type int
. During each loop iteration, the variable numGuests
takes on a new value. Look at Figure 11-4 to see these values. The initial value is 1
. The next value is 4
. After that comes 2
. And so on.
Where is the loop finding all these numbers? The answer lies in the loop’s range-of-values
. In Listing 11-3, the loop’s range-of-values
is guests
. So, during the initial loop iteration, the value of numGuests
is guests[0]
(which is 1
). During the next iteration, the value of numGuests
is guests[1]
(which is 4
). After that comes guests[2]
(which is 2
). And so on.
So for example, if you add an assignment statement that changes the value of numGuests
in Listing 11-3, this statement has no effect on any of the values stored in the guests
array. To drive this point home, imagine that business is bad and I’ve filled my hotel’s guests
array with zeros. Then I execute the following code:
for (int numGuests : guests) {
numGuests += 1;
out.print(numGuests + “ “);
}
out.println();
for (int numGuests : guests) {
out.print(numGuests + “ “);
}
The numGuests
variable takes on values stored in the guests array. But the numGuests += 1
statement doesn’t change the values stored in this guests
array. The code’s output looks like this:
1 1 1 1 1 1 1 1 1 1
0 0 0 0 0 0 0 0 0 0
Searching
You’re sitting behind the desk at the Java Motel. Look! Here comes a party of five. These people want a room, so you need software that checks whether a room is vacant. If one is, the software modifies the GuestList.txt
file (refer to Figure 11-3) by replacing the number 0 with the number 5. As luck would have it, the software is right on your hard drive. The software is shown in Listing 11-4.
Listing 11-4: Do You Have a Room?
import static java.lang.System.out;
import java.util.Scanner;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
class FindVacancy {
public static void main(String args[])
throws IOException {
Scanner keyboard = new Scanner(System.in);
Scanner diskScanner =
new Scanner(new File(“GuestList.txt”));
int guests[] = new int[10];
int roomNum;
for (roomNum = 0; roomNum < 10; roomNum++) {
guests[roomNum] = diskScanner.nextInt();
}
roomNum = 0;
while (roomNum < 10 && guests[roomNum] != 0) {
roomNum++;
}
if (roomNum == 10) {
out.println(“Sorry, no v cancy”);
} else {
out.print(“How many people for room “);
out.print(roomNum);
out.print(“? “);
guests[roomNum] = keyboard.nextInt();
PrintStream listOut =
new PrintStream(“GuestList.txt”);
for (roomNum = 0; roomNum < 10; roomNum++) {
listOut.print(guests[roomNum]);
listOut.print(“ “);
}
}
}
}
Figures 11-5 through 11-7 show the running of the code in Listing 11-4. Back in Figure 11-3, the motel starts with two vacant rooms — Rooms 3 and 8. (Remember, the rooms start with Room 0.) The first time that you run the code in Listing 11-4, the program tells you that Room 3 is vacant and puts five people into the room. The second time you run the code, the program finds the remaining vacant room (Room 8) and puts a party of ten in the room. (What a party!) The third time you run the code, you don’t have any more vacant rooms. When the program discovers this, it displays the message Sorry, no v cancy
, omitting at least one letter in the tradition of all motel neon signs.
The code in Listing 11-4 uses tricks from other chapters and sections of this book. The code’s only brand-new feature is the use of PrintStream
to write to a disk file. Think about any example in this book that calls System.out.print
, out.println
, or their variants. What’s really going on when you call one of these methods?
The thing called System.out
is an object. The object is defined in the Java API. In fact, System.out
is an instance of a class named java.io.PrintStream
(or just PrintStream
to its close friends). Now each object created from the PrintStream
class has methods named print
and println
. Just as each Account
object in Listing 7-3 has a display
method, and just as the DecimalFormat
object in Listing 10-1 has a format
method, so the PrintStream
object named out
has print
and println
methods. When you call System.out.println
, you’re calling a method that belongs to a PrintStream
instance.
Okay, so what of it? Well, System.out
always stands for some text area on your computer screen. If you create your own PrintStream
object and you make that object refer to a disk file, that PrintStream
object refers to the disk file. When you call that object’s print
method, you write text to a file on your hard drive.
So in Listing 11-4, when you say
PrintStream listOut =
new PrintStream(“GuestList.txt”);
listOut.print(guests[roomNum]);
listOut.print(“ “);
you’re telling Java to write text to a file on your hard drive — the GuestList.txt
file.
That’s how you update the count of guests staying in the hotel. When you call listOut.print
for the number of guests in Room 3, you may print the number 5. So, between Figures 11-5 and 11-6, a number in the GuestList.txt
file changes from 0 to 5. Then in Figure 11-6, you run the program a second time. When the program gets data from the newly written GuestList.txt
file, Room 3 is no longer vacant. So this time, the program suggests Room 8.
Arrays of Objects
The Java Motel is open for business, now with improved guest registration software! The people who brought you this chapter’s first section are always scratching their heads, looking for the best ways to improve their services. Now, with some ideas from object-oriented programming, they’ve started thinking in terms of a Room
class.
“And what,” you ask, “would a Room
instance look like?” That’s easy. A Room
instance has three properties — the number of guests in the room, the room rate, and a smoking/nonsmoking stamp. Figure 11-8 illustrates the situation.
Listing 11-5 shows the code that describes the Room
class. As promised, each instance of the Room
class has three fields: the guests, rate, and smoking fields. (A false
value for the boolean field, smoking
, indicates a nonsmoking room.) In addition, the entire Room
class has a static
field named currency
. This currency
object makes room rates look like dollar amounts.
Listing 11-5: So This is What a Room Looks Like!
import static java.lang.System.out;
import java.util.Scanner;
import java.text.NumberFormat;
public class Room {
private int guests;
private double rate;
private boolean smoking;
private static NumberFormat currency =
NumberFormat.getCurrencyInstance();
public void readRoom(Scanner diskScanner) {
guests = diskScanner.nextInt();
rate = diskScanner.nextDouble();
smoking = diskScanner.nextBoolean();
}
public void writeRoom() {
out.print(guests);
out.print(“ ”);
out.print(currency.format(rate));
out.print(“ ”);
out.println(smoking ? “yes” : “no”);
}
}
Listing 11-5 has a few interesting quirks, but I’d rather not describe them until after you see all the code in action. That’s why, at this point, I move right on to the code that calls the Listing 11-5 code. After you read about arrays of rooms (shown in Listing 11-6), check out my description of the Listing 11-5 quirks.
Using the Room class
So now you need an array of rooms. The code to create such a thing is in Listing 11-6. The code reads data from the RoomList.txt
file. (Figure 11-9 shows the contents of the RoomList.txt
file.)
Figure 11-10 shows a run of the code in Listing 11-6.
Listing 11-6: Would You Like to See a Room?
import static java.lang.System.out;
import java.util.Scanner;
import java.io.File;
import java.io.IOException;
class ShowRooms {
public static void main(String args[])
throws IOException {
Room rooms[];
rooms = new Room[10];
Scanner diskScanner =
new Scanner(new File(“RoomList.txt”));
for (int roomNum = 0; roomNum < 10; roomNum++) {
rooms[roomNum] = new Room();
rooms[roomNum].readRoom(diskScanner);
}
out.println(“Room Guests Rate Smoking?”);
for (int roomNum = 0; roomNum < 10; roomNum++) {
out.print(roomNum);
out.print(“ ”);
rooms[roomNum].writeRoom();
}
}
}
Say what you want about the code in Listing 11-6. As far as I’m concerned, only one issue in the whole listing should concern you. And what, you ask, is that issue? Well, to create an array of objects — as opposed to an array made up of primitive values — you have to do three things: make the array variable, make the array itself, and then construct each individual object in the array. This is different from creating an array of int
values or an array containing any other primitive type values. When you create an array of primitive type values, you do only the first two of these three things.
To help make sense of all this, follow along in Listing 11-6 and Figure 11-11 as you read the following points:
Room rooms[];
: This declaration creates a rooms
variable. This variable is destined to refer to an array (but doesn’t yet refer to anything at all).
rooms = new Room[10];
: This statement reserves ten slots of storage in the computer’s memory. The statement also makes the rooms
variable refer to the group of storage slots. Each slot is destined to refer to an object (but doesn’t yet refer to anything at all).
r
ooms[roomNum] = new Room();
: This statement is inside a for
loop. The statement is executed once for each of the ten room numbers. For example, the first time through the loop, this statement says rooms[0] = new Room()
. That first time around, the statement makes the slot rooms[0]
refer to an actual object (an instance of the Room
class).
Although it’s technically not considered a step in array making, you still have to fill each object’s fields with values. For instance, the first time through the loop, the readRoom
call says rooms[1].readRoom(diskScanner)
, which means, “Read data from the RoomList.txt
file into the rooms[1]
object’s fields (the guests
, rate
, and smoking
fields).” Each time through the loop, the program creates a new object and reads data into that new object’s fields.
You can squeeze the steps together just as you do when creating arrays of primitive values. For instance, you can do the first two steps in one fell swoop, like this:
Room rooms[] = new Room[10];
You can also use an array initializer. (For an introduction to array initializers, see the section, “Using an array initializer,” earlier in this chapter.)
Yet another way to beautify your numbers
You can make numbers look nice in plenty of ways. If you take a peek at some earlier chapters, for example, you can see that Listing 7-7 uses printf,
and Listing 10-1 uses a DecimalFormat
. But in Listing 11-5, I display a currency amount. I use the NumberFormat
class with its getCurrencyInstance
method.
If you compare the formatting statements in Listings 10-1 and 11-5, you don’t see much difference.
One listing uses a constructor; the other listing calls getCurrencyInstance
. The getCurrencyInstance
method is a good example of what’s called a factory method. A factory method is a convenient tool for creating commonly used objects. People always need code that displays dollar amounts. So the getCurrencyInstance
method creates a dollar format without forcing you to write new DecimalFormat (“$###0.00;($###0.00)”)
.
Like a constructor, a factory method returns a brand-new object. But unlike a constructor, a factory method has no special status. When you create a factory method, you can name it anything you want. When you call a factory method, you don’t use the keyword new
.
One listing uses DecimalFormat
; the other listing uses NumberFormat
. A decimal number is a certain kind of number. (In fact, a decimal number is a number written in the base-10 system.) Accordingly, the DecimalFormat
class is a subclass of the NumberFormat
class. The DecimalFormat
methods are more specific, so for most purposes, I use DecimalFormat
. But it’s harder to use the DecimalFormat
class’s getCurrencyInstance
method. So for programs that involve money, I tend to use NumberFormat
.
Both listings use format
methods. In the end, you just write something like currency.format(rate)
or decFormat.format(average)
. After that, Java does the work for you.
The conditional operator
Listing 11-5 uses an interesting doodad called the conditional operator. This conditional operator takes three expressions and returns the value of just one of them. It’s like a mini if
statement. When you use the conditional operator, it looks something like this:
conditionToBeTested
? expression1
: expression2
The computer evaluates the conditionToBeTested
condition. If the condition is true, the computer returns the value of expression1
. But, if the condition is false, the computer returns the value of expression2
.
So, in the code
smoking ? “yes” : “no”
the computer checks whether smoking
has the value true
. If so, the whole three-part expression stands for the first string, “yes”
. If not, the whole expression stands for the second string, “no”
.
In Listing 11-5, the call to out.println
causes either “yes”
or “no”
to display. Which string gets displayed depends on whether smoking
has the value true
or false
.
Command Line Arguments
Once upon a time, most programmers used a text-based development interface. To run the Displayer
example in Chapter 3, they didn’t select Run from a menu in a fancy integrated development environment. Instead they typed a command in a plain-looking window, usually with white text on a black background. Figure 11-12 illustrates the point. In Figure 11-12, I type the words java Displayer
, and the computer responds with my Java program’s output (the words You’ll love Java!
).
The plain-looking window goes by the various names, depending on the kind of operating system that you use. In Windows, a text window of this kind is a command prompt window. On a Macintosh and in Linux, this window is the terminal. Some versions of Linux and UNIX call this window a shell.
Anyway, back in ancient times, you could write a program that sucked up extra information when you typed the command to launch the program. Figure 11-13 shows you how this worked.
In Figure 11-13, the programmer types java MakeRandomNumsFile
to run the MakeRandomNumsFile
program. But the programmer follows java MakeRandomNumsFile
with two extra pieces of information; namely, MyNumberedFile.txt
and 5
. When the MakeRandomNumsFile
program runs, the program sucks up two extra pieces of information and uses them to do whatever the program has to do. In Figure 11-13, the program sucks up MyNumberedFile.txt 5
, but on another occasion the programmer might type SomeStuff 28
or BunchONumbers 2000
. The extra information can be different each time you run the program.
So the next question is, “How does a Java program know that it’s supposed to snarf up extra information each time it runs?” Since you first started working with Java, you’ve been seeing this String args[]
business in the header of every main
method. Well, it’s high time you found out what that’s all about. The parameter args[]
is an array of String
values. These String
values are called command line arguments.
Using command line arguments in a Java program
Listing 11-7 shows you how to use command line arguments in your code.
Listing 11-7: Generate a File of Numbers
import java.util.Random;
import java.io.PrintStream;
import java.io.IOException;
class MakeRandomNumsFile {
public static void main(
String args[]
)
throws IOException {
Random generator = new Random();
if (args.length < 2) {
System.out.println
(“Usage: MakeRandomNumsFile filename number”);
System.exit(1);
}
PrintStream printOut = new PrintStream(args[0]);
int numLines = Integer.parseInt(args[1]);
for (int count = 1; count <= numLines; count++) {
printOut.println(generator.nextInt(10) + 1);
}
}
}
When the code in Listing 11-7 begins running, the args
array gets its values. In the main
method of Listing 11-7, the array component args[0]
automatically takes on the value “MyNumberedFile.txt”
, and args[1]
automatically becomes “5”
. So the program’s assignment statements end up having the following meaning:
PrintStream printOut = new PrintStream(“MyNumberedFile.txt”);
int numLines = Integer.parseInt(“5”);
The program creates a file named MyNumberedFile.txt
and sets numLines
to 5
. So later in the code, the program randomly generates five values and puts those values into MyNumberedFile.txt
. One run of the program gives me the file shown in Figure 11-14.
Notice how each command line argument in Listing 11-7 is a String
value. When you look at args[1]
, you don’t see the number 5 — you see the string “5”
with a digit character in it. Unfortunately, you can’t use that “5”
to do any counting. To get an int
value from “5”
, you have to apply the parseInt
method. (Again, see Listing 11-7.)
The parseInt
method lives inside a class named Integer. So, to call parseInt
, you preface the name parseInt with the word Integer. The Integer
class has all kinds of handy methods for doing things with int
values.
Checking for the right number of command line arguments
What happens if the user makes a mistake? What if the user forgets to type the number 5
on the first line in Figure 11-13?
Then the computer assigns “MyNumberedFile.txt”
to args[0]
, but it doesn’t assign anything to args[1]
. This is bad. If the computer ever reaches the statement
int numLines = Integer.parseInt(args[1]);
the program crashes with an unfriendly ArrayIndexOutOfBoundsException
.
So, what do you do about this? In Listing 11-7, you check the length of the args
array. You compare args.length
with 2. If the args
array has fewer than two components, you display a message on the screen and exit from the program. Figure 11-15 shows the resulting output.
Using Java Collections
Arrays are nice, but arrays have some serious limitations. Imagine that you store customer names in some predetermined order. Your code contains an array, and the array has space for 100 names.
String name[] = new String[100];
for (int i = 0; i < 100; i++) {
name[i] = new String();
}
All is well until, one day, customer number 101 shows up. As your program runs, you enter data for customer 101, hoping desperately that the array with 100 components can expand to fit your growing needs.
No such luck. Arrays don’t expand. Your program crashes with an ArrayIndexOutOfBoundsException
.
“In my next life, I’ll create arrays of length 1,000,” you say to yourself. And when your next life rolls around, you do just that.
String name[] = new String[
1000
];
for (int i = 0; i <
1000
; i++) {
name[i] = new String();
}
But during your next life, an economic recession occurs. Instead of having 101 customers, you have only 3 customers. Now you’re wasting space for 1,000 names when space for 3 names would do.
And what if no economic recession occurs? You’re sailing along with your array of size 1,000, using a tidy 825 spaces in the array. The components with indices 0 through 824 are being used, and the components with indices 825 through 999 are waiting quietly to be filled.
One day, a brand-new customer shows up. Because your customers are stored in order (alphabetically by last name, numerically by Social Security number, whatever), you want to squeeze this customer into the correct component of your array. The trouble is that this customer belongs very early on in the array, at the component with index 7. What happens then?
You take the name in component number 824 and move it to component 825. Then you take the name in component 823 and move it to component 824. Take the name in component 822 and move it to component 823. You keep doing this until you’ve moved the name in component 7. Then you put the new customer’s name into component 7. What a pain! Sure, the computer doesn’t complain. (If the computer has feelings, it probably likes this kind of busy work.) But as you move around all these names, you waste processing time, you waste power, and you waste all kinds of resources.
“In my next life, I’ll leave three empty components between every two names.” And of course, your business expands. Eventually you find that three aren’t enough.
Collection classes to the rescue
The issues in the previous few paragraphs aren’t new. Computer scientists have been working on these issues for a long time. They haven’t discovered any magic one-size-fits-all solution, but they’ve discovered some clever tricks.
The Java API has a bunch of classes known as collection classes. Each collection class has methods for storing bunches of values. And each collection class’s methods use some clever tricks. For you, the bottom line is as follows: Certain collection classes deal as efficiently as possible with the issues raised in the previous few paragraphs. If you have to deal with such issues when writing code, you can use these collection classes and call the classes’ methods. Instead of fretting about a customer whose name belongs in position 7, you can just call a class’s add
method. The method inserts the name at a position of your choice and deals reasonably with whatever ripple effects have to take place. In the best circumstances, the insertion is very efficient. In the worst circumstances, you can rest assured that the code does everything the best way it can.
Using an ArrayList
One of the most versatile of Java’s collection classes is the ArrayList
. Listing 11-8 shows you how it works.
Listing 11-8: Working with a Java Collection
import static java.lang.System.out;
import java.util.Scanner;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
class ShowNames {
public static void main(String args[])
throws IOException {
ArrayList<String> people =
new ArrayList<String>();
Scanner diskScanner =
new Scanner(new File(“names.txt”));
while (diskScanner.hasNext()) {
people.add(diskScanner.nextLine());
}
people.remove(0);
people.add(2, “Jim Newton”);
for (String name : people) {
out.println(name);
}
}
}
Figure 11-16 shows you a sample names.txt
file. The code in Listing 11-8 reads that names.txt
file and prints the stuff in Figure 11-17.
All the interesting things happen when you execute the remove
and add
methods. The variable named people
refers to an ArrayList
object. When you call that object’s remove
method
people.remove(0);
you eliminate a value from the list. In this case, you eliminate whatever value is in the list’s initial position (the position numbered 0). So in Listing 11-8, the call to remove
takes the name Barry Burd
out of the list.
That leaves only eight names in the list, but then the next statement,
people.add(2, “Jim Newton”);
inserts a name into position number 2. (After Barry is removed, position number 2 is the position occupied by Harry Spoonswagler, so Harry moves to position 3, and Jim Newton becomes the number 2 man.)
Notice that an ArrayList
object has two different add
methods. The method that adds Jim Newton has two parameters: a position number and a value to be added. Another add
method
people.add(diskScanner.nextLine());
takes only one parameter. This statement takes whatever name it finds on a line of the input file and appends that name to the end of the list. (The add
method with only one parameter always appends its value to what’s currently the end of the ArrayList
object.)
The last few lines of Listing 11-8 contain an enhanced for
loop. Like the loop in Listing 11-3, the enhanced loop in Listing 11-8 has the following form:
for (
variable-type
variable-name
: range-of-values
)
In Listing 11-8, the variable-type is String
, the variable-name is name
, and the range-of-values includes the things stored in the people
collection. During an iteration of the loop, name
refers to one of the String
values stored in people
. (So if the people
collection contains nine values, then the for
loop goes through nine iterations.) During each iteration, the statement inside the loop displays a name on the screen.
Using generics (hot stuff!)
Look again at Listing 11-8 and notice the funky ArrayList
declaration:
ArrayList
<String>
people = new ArrayList<String>
();
Starting with Java 5.0, each collection class is generified. That ugly-sounding word means that every collection declaration should contain some angle-bracketed stuff, such as <String>
. The thing that’s sandwiched between <
and >
tells Java what kinds of values the new collection may contain. For example, in Listing 11-8, the words ArrayList<String> people
tell Java that people
is a bunch of strings. That is, the people
list contains String
objects (not Room
objects, not Account
objects, not Employee
objects, nothing other than String
objects).
In Listing 11-8 the words ArrayList<String> people
say that the people
variable can refer only to a collection of String
values. So from that point on, any reference to an item from the people
collection is treated exclusively as a String
. If you write
people.add(new
Room
());
then the compiler coughs up your code and spits it out because a Room
(created in Listing 11-5) isn’t the same as a String
. (This coughing and spitting happens even if the compiler has access to the Room
class’s code — the code in Listing 11-5.) But the statement
people.add(
“George Gow”
);
is just fine. Because “George Gow”
has type String
, the compiler smiles happily.
Testing for the presence of more data
Here’s a pleasant surprise. When you write a program like the one shown previously in Listing 11-8, you don’t have to know how many names are in the input file. Having to know the number of names may defeat the purpose of using the easily expandable ArrayList
class. Instead of looping until you read exactly nine names, you can loop until you run out of data.
The Scanner
class has several nice methods like hasNextInt
, hasNextDouble
, and plain old hasNext
. Each of these methods checks for more input data. If there’s more data, the method returns true
. Otherwise, the method returns false
.
Listing 11-8 uses the general purpose hasNext
method. This hasNext
method returns true
as long as there’s anything more to read from the program’s input. So after the program scoops up that last Hugh R. DaReader
line in Figure 11-16, the subsequent hasNext
call returns false
. This false
condition ends execution of the while
loop and plummets the computer toward the remainder of the Listing 11-8 code.