Mini-Lab: More Fish!

Using ArrayLists


This set of Mini-Lab Exercises is part of a series in which students build a small program with several fish moving around in an aquarium. The set includes the following exercises:

Each section contains an Introduction to a problem or task, descriptions or examples of one or more Concepts to apply in solving the problem or completing the task, and an Exercise.

In the exercises that precede this one, students will have created three fish that move randomly back and forth in an aquarium, being careful not to hit the sides. Students should be familiar with basic for loops, simple selection statements, prompting for input, and the java.util.Random class.



A School of Fish

Introduction

A more realistic simulation of an aquarium would have more than two or three fish. We could modify the simulation so that it supports four fish, five fish, or twelve fish in that many variables, but if we "hard-code" the number of fish into the program in this way, then we must modify and re-compile the program to change the number of fish in the aquarium. We must also repeat statements, such as the code to move fish, four, five, or twelve times.

A better alternative would be to ask the user how many fish to place in the aquarium and then store them in a collection object. We can use a Java ArrayList (from the java.util package) to store the collection of fish.

Concept: Expandable Linear Indexed Data Structure

Java provides a number of classes that provide different ways to keep track of a group or collection of objects. The Java ArrayList class is one of these. An ArrayList object is a list of items with several important characteristics:

The syntax for creating a Java ArrayList is:

where the <Type> portion indicates what type of object the ArrayList will hold. For example, the following code fragment creates an empty deck of cards:


Concept: Appending to a List

The add method in the ArrayList class adds a new item to the end of the list. For example, if deck is the Java ArrayList of playing cards defined above, then either of the following code fragments adds a new Card to the end of the deck:

Adding an object using a variable Adding an object without a variable
for (i = 0; i < 52; i++ )
{
    Card aCard = new Card();
    deck.add(aCard);
}
for (i = 0; i < 52; i++ )
{
    deck.add(new Card());
}

Exercise: Constructing a List

  • Replace the three fish variables in the AquaSimApplication class with a single variable that will represent the list of fish in the aquarium. Give it a name that indicates its meaning or purpose in the program. Initialize your new variable to refer to an initially-empty list.
  • Don't forget to import the appropriate library class (java.util.ArrayList).
  • Using a loop, construct three new fish and add them to your ArrayList. Don't forget to include the code that selects a color for each new fish in the loop. You will still need to pass the aquarium and a color to the AquaFish constructor. (You should only need one copy of this code, though, not 3 copies.)
  • Add each fish to the aquarium. If you did not use a variable when adding fish to the list (similar to the example above on the right), you will have to "get" the fish from the list to add it to the aquarium, which is covered in the next section.
  • Try to test your changes.

  • Stop and Think

    Do you expect your program to compile or to work? If not, why not? What do you need to do to test the changes you have made so far?

    Commenting Out Code: It is always difficult to test modified code part-way through a change. At this point, the fish that you had before no longer exist. To test that you have constructed the list correctly, you want to compile and run your program. To do this, you will have to "comment out" the code in the main method that no longer works (the loop that repeatedly moves the fish and redisplays the aquarium). You will update this code in the next few exercises, which is easier to do when you can see what the old code was, which is why it is better to comment out the code rather than deleting it until you have implemented the replacement code.

    Test your changes: Once you have commented out the loop that moves the fish, you can test your new code to create a list of fish. Make sure that the first call to show the aquarium, after everything has been set up but before the fish start moving, is not commented out. Don't be surprised, though, when your program no longer displays moving fish!

Now that you have a list of fish, and don't have to have a separate variable for each one, let's let the user decide how many fish to include. The steps for doing this are similar to what you did in an earlier lab to allow the user to specify how many times the fish should move.

Exercise: Let the User Decide

  • Read the class documentation for the AquaSimGUI class, to learn how to use its three-parameter constructor to prompt the user for the number of fish. Modify your program to use this constructor, updating the internal documentation as appropriate.

  • Modify the loop you created to construct the fish, so that it uses the number of fish specified by the user. (Stop and Think: Does the order in which you are constructing the aquarium, the fish in the aquarium, and the graphical user interface matter? Do you need to change the order in which they are constructed?)
  • Test your program to make sure that it correctly constructs and displays the number of fish specified by the user.



Catching Fish

Introduction

One of the reasons we switched to using a collection object was to avoid having to duplicate code for each fish, since that would not scale up well if we want to put many fish in the aquarium!  We can access all of the fish sequentially using either of two different for loop variations.

Concept: Indexed Random Access

To access a specific item in a list directly, we specify its index. List indices start at 0, not at 1, so if there are N items in a list, the valid indices are 0 to N-1. The expression deck.size() indicates how many items are in the list (the N for that list).

For example, if deck is a Java ArrayList of playing cards, then we could use the following statements to display various cards in the deck.

Card to display Code to display it
first card in the deck
deck.get(0).display();
sixth card in the deck
deck.get(5).display();
(assuming that there are at least 6 cards in the deck)
last card in the deck
int lastIndex = deck.size() - 1;
deck.get(lastIndex).display();

        OR

deck.get(deck.size()-1).display();

Indices can also be used to modify an element in a data structure. For example, to swap the 1st and 52nd cards in the deck, the following code might be used:

Exercise

The code to move your three fish is still commented out, but we can add some temporary code to test accessing specific fish in the list.

  • Add code to retrieve the first fish in the list and move it once, then redisplay the aquarium. Test your code to see that just one fish moves.
  • Do the same for the last fish you created and added to the list.



A School In Motion

Introduction

Now let's bring back the behavior to have the fish move repeatedly, but this time moving all the fish in the list, not three specific fish.

Concept: Access All Items in a Collection

You can use the same kind of for loop you've used before to access all the items in a collection, OR you can use the simpler for-each style of for loop. The structure of this type of loop is shown below. <Type> should be replaced with the type of item in the container; the <variable> refers to a different item from the container each time through the loop.

 for ( <Type> <variable> : <container> )
 {
        <repeated action>
 }

Notice that the for-each type of loop introduces a new variable ( <Type> <variable> ) that refers to a new item from the list every time through the loop, just as the traditional for loop does. This is often read as "For each Card instance in the deck (called cardInDeck), display the card."

Example 1: Traditional For loop Example 2: For each loop
for ( int i = 0; i < deck.size(); i++ )
{
    Card cardInDeck = deck.get(i);
    cardInDeck.display();
}
WARNING: Be sure stay less than the size of the
collection to avoid off-by-one errors.
for ( Card cardInDeck : deck )
{
    cardInDeck.display();
}

The for-each type of loop is simpler to write and avoids off-by-one errors (starting with the wrong index, going too far, not going far enough). It isn't as general-purpose though.

Limitations of the for each structure: A for each statement can only be used when all the elements in the list are to be accessed and each element's position in the ArrayList doesn't matter. If you want to add items to or delete items from the list, replace the items in the list, or determine where in the list an object is located, then you cannot use a for each statment.

Exercise

  • Update your temporary testing code to tell all the fish in the list to move once, then (after the loop) redisplay the aquarium. Test your code.

It seems clear that we will want to loop through the steps in the simulation, as we are already doing. It also seems clear that we will want to loop through all the fish. The question is:

We can't process all the fish and then process all the simulation steps, or vice versa. Thus, we need to nest one of the loops inside the other. To decide which loop gets nested inside of which, consider the following algorithmic structures in which we assume that we have 25 fish in the aquarium and we want to run the simulation 100 times.

For each fish in the collection:
        Move 100 times (an inner loop).
        Display the aquarium.
For each step in the simulation:
        Move the 25 fish once (an inner loop).
        Display the aquarium.

We do not want the first fish to move 100 times, followed by the second fish moving 100 times, followed by the third fish moving 100 times. Instead, we want all 25 fish to move once, then all 25 fish to move again. This corresponds to the second solution above.

Exercise

  • Remove the remaining comments around the code that runs through the steps of the simulation, moving and displaying the fish in the aquarium.
  • Inside the simulation loop, replace the code that moves the three named fish with an inner loop that will move all the fish in your collection. (Each will still change direction when it has to or when it randomly chooses to.) Remember that you want to redisplay the aquarium for each simulation step, but not for each fish individually. (Stop and Think: The outer simulation loop uses a traditional for loop because it isn't stepping through the objects in a collection. For the inner loop, though, you have a choice between the two types of loop. To give yourself practice with both types of loop, use the for-each style loop for the inner loop.
  • Test your modifications.
  • Update your internal documentation (regular comments) and your class documentation (class and method javadoc comments) to reflect any changes in the behavior of the code, the class, the methods, or the program as a whole.