next up previous
Next: Week 8 Up: Week 7 Previous: forever And A Day:

Arrays: Giving your Loop Something(s) To Work On

We've seen some examples of loops now, and we know that a loop is something that allows us to control the flow (the order of execution of statements) in a program. What you may not have noticed is that although we can execute the block of code in a loop over and over and therefore we can read and write to memory over and over, we can only read and write to the same memory locations. We've talked about the reading and writing to memory and we've shown that using variable names causes the computer to read the value of that variable from memory, and using the '=' symbol causes the computer to write a value to memory. What we're saying here is that (as far as we know so far) in a loop, we can only read and write to the exactly the same locations each time we execute the code of the loop.

Now this might be OK depending on what we're doing. For example, imagine that we want to find out how many times we can divide 5124.65 by 2 before the result is less than 126.4. We could do this as follows:


double value = 5124.65;
int i = 0;
while (value >= 126.4){
        value /= 2.0;
        i++;
}
System.out.println(`` We can divide `` + i + `` times.'');

Here we read and write to the memory locations associated with i and value each time through the loop - the same locations each time. This is perfectly fine for the purposes of this task, but there are tasks where we'd like to read and write to a different location each time.

For example, imagine that we've created a student registration system, and we've managed to register several thousand students in our university. For each student, we've created an object, and that object contains the name of the person, her age, and so on. Now what we'd like to do is to calculate the average age of the students in our university. How do we do this?

Well, we could write a line of code that looks like this:


double averageAge = ( s1.getAge() + s2.getAge() + s3.getAge() + s4.getAge() + ... + s3428.getAge()) / 3428;

The ... in the line above refers to the fact that we need to have 3423 more method calls like s5.getAge() and so on in order to calculate the average. First of all, this is a very long line of programming. Secondly, if we write a line like this, the program will only be useful if there are exactly 3428 students. If there are more or less, we'll either get an incorrect average, or get an error.

You can see that this doesn't give us very much flexibility. We'd like to be able to write our program so that no matter how many students show up, the average age will be calculated properly. In addition, it would be nice to have a way of doing this so that we don't have to write thousands of method calls. Clearly what would be nice is if we could create a loop of some kind so that we could access the age of a different student each time through the loop. In this way, we could calculate the sum of the ages of the students by keeping a sum of all the ages so far, and each time through the loop we add the age of the next (different ) student.

What we can do with loops already is to count -- just as in the example at the top. By incrementing the value of a variable each time through the loop, we can count the number of times we execute the code in the loop. So if we know that 3428 students are registered, wouldn't it be nice to execute the loop code 3428 times, and each time through we use the value of our 'counter' to refer to a different student?

It turns out that we can do this, and this is how:

First of all, we create an object which can contain references to as many objects as we like The following statement allocates memory to hold references to 5000 student objects, for example:


Student [] s = new Student[5000];

Here s contains a reference to an object, and that object has enough memory to hold 5000 references to student objects. The object which holds the reference is called an array, and one of its fields is an int called length which stores the number of references that the array object can store. (5000 in this case).

Note that we haven't yet created the 5000 Student objects yet, but we have created an object that will store the references to the 5000 Student objects.

Once we've done that, we need to register the students -- find out their names, ages, and so on, create the objects, and fill in the fields of the objects. For example, in order to create the Student objects, we might do something like:


for (int i = 0; i < s.length; i++){
        s[i] = new Student(.....);
}

But how do we refer to individual students if we've created a whole bunch of references to student objects, as above? We do so by using an index or counter as follows:


s[0].setName{``Fred Flintstone'');

Here 0 is the index. Since we've created 5000 students, we can refer to each individual student as s[0], s[1], ..., s[4999]. Notice that with 5000 students, our indices run from 0 to 4999.

This is great! Now we can calculate the average age of students like this: (assuming that 3428 students have been registered - which means that their names and ages have been set in each object - and that we've stored the total number of registered students in the variable numStudents:


int ageSum =  0;
for(int i = 0; i < numStudents; i++){
ageSum += s[i].getAge();
}

double averageAge = ((double) ageSum) / numStudents;

Now this is a lot easier than writing 3428 getAge() method calls! And it lets us process an arbitrary number of students. Or does it? What happens if more than 5000 students show up? In other words,what happens if we use something like s[5000].getAge() in our program somewhere? Since there is no object s[5000] (remember, we only instantiated 0 through 4999 students) this will cause an error. So if we were writing code this way, what we would want to do is to choose a number of students to instantiate so that we're sure that it's greater than the number of students that will actually show up, but not so big that it uses up all the memory in the computer.... We'll see another way of dealing with this issue later, but for now we'll have to live with this approach.

The name of this 'bunch of students' is an array, and you can create arrays of any type you like:


int [] someIntArray = new int[100];
double [] someDoubleArray = new double[7];
boolean [] someBooleanArray = new boolean[10];

In the examples above, we created arrays of some primitive data types, while previously we created an array of a user defined data type, Student. Things work pretty much the same way in both cases, except an array of objects is actually an array of references.

An array is itself actually an object. Therefore, the following statement:


Student [] s;

only allocates some memory in the run-time stack to hold the reference to the array:

Figure 21: Memory model after Student [] s; has been executed.
\begin{figure}
\begin{center}
\epsfxsize =5in {\epsfbox {memmodelarray1.ps}} \\
\end{center}\end{figure}

This is the same as for any other object. The statement


s = new Student[6];

actually instantiates the array object, and stores the reference to that object in the memory we already allocated on the run-time stack (that is the memory location that s refers to):

Figure 22: Memory model after s = new Student[6]; has been executed.
\begin{figure}
\begin{center}
\epsfxsize =5in {\epsfbox {memmodelarray2.ps}} \\
\end{center}\end{figure}

But the array object holds references to the actual objects of the array. So the array is an object, and in this case. each element of the array is an object! Notice that the references to the Student objects haven't been filled in yet; the array object contains null references. In fact, we haven't even created the Student objects yet, which we'll do next.

Finally, if we execute


for (int i = 0; i < s.length; i++){
        s[i] = new Student(.....);
}
then we fill in the array with references to the newly created Student objects, and the picture looks like this:

Figure 23: Memory model after Student objects have been instantiated.
\begin{figure}
\begin{center}
\epsfxsize =5in {\epsfbox {memmodelarray3.ps}} \\
\end{center}\end{figure}

Two limitations that must be observed with arrays are:

  1. The elements in an array must be of the same type
  2. Once we've created an array of a certain length, we can't change the length

We'll come across other data structures that don't have these limitations....


next up previous
Next: Week 8 Up: Week 7 Previous: forever And A Day:
Chris Trendall
Copyright ©Chris Trendall, 2001. All rights reserved.

2001-12-09