4B. Arrays and Java
Executive Summary
Arrays are objects composed of sequences of data items all of which are of the same variety. Thus we might have an array of 100 ints or 5 trafficLights. Not only are arrays frequently used by themselves, they are also frequently used when implementing other more advanced data structures, such as stacks, that we will examine in later chapters. This chapter reviews arrays from a Java perspective.
In Java, arrays are a kind of object, consisting of a sequence of 1 or things, each of which is a simple variable of the same type as all of the others or is an object of the same class as all of the others. Arrays are one-dimensional or multi-dimensional, must be first declared and then have memory allocated for them, and in the case of an array of objects each object must be allocated in addition. Allocation is via the new operator. Array subscripts start from 0 and Java automatically checks for array references that are out of bounds.
C language note - the most important conceptual difference between arrays in Java and arrays in C is that arrays in Java must be allocated. A further point of possible confusion is that, unless it is an array of variables of an elementary data type like int each element of the array must also be allocated, for example by using a loop to iterate over the entire array, allocating each element in turn.
Outline 4B.1 Introduction 4B.2 One dimensional arrays 4B.3 Multidimensional arrays 4B.4 Problems4B.2 One dimensional arrays =>FIND SIMILAR
The symbol [] signifies an array. Hence, while
int number;
declares single integer named number,
int numbers[];
declares an array of integers called numbers of as yet undetermined size. Because arrays have their memory allocated after being declared, the size does not have to be known at declaration time in Java, so the declaration statement does need to define the size and therefore does not.
Prior to being used, the array must have memory for it allocated, and the allocation statement defines its size. For example,
new numbers[1000];
allocates enough memory for an array of 1000 integers (which at the Java integer size of 32 bits amounts to 4000 8-bit bytes of memory). All subsequent references to the array are checked automatically to ensure that the subscript is an integer of value from 0 through 999.
The declaration and allocation, two separate steps, can nevertheless be combined into one statement, as in
int numbers[]=new int[1000];
which does exactly the same thing. Arrays of a simple data type can also be initialized at allocation time, as in
int numbers[]={10,9,8,7,6,5,4,3,2,1};
which sets the size of numbers at 10 integers and initializes numbers[0] to 10, numbers[1] to 9, etc.
A third way to initialize an array is to assign it to another array, as in
which makes morenums and numbers handles (pointers to) the same thing, a sequence of 10 integers from 10 down to 1. In other words, moreNums is declared as the handle (pointer to) an array, and its value can be the same as the value of numbers, namely that particular sequence of 10 integers.
************************************************************************ Figure 4B.2-1: diagram of two arrays pointing to the same sequence of numbers in the heap. ***********************************************************************Now suppose instead of an array of integers or other simple data type, we declare an array of objects. For example, perhaps we need an array of the following "nodes." (For the present, the structure of this node class definition has no bearing on the current discussion. However, if you are curious about it, the node class shown is for constructing a chain of nodes, where each node contains a data item and points to the next and previous nodes in the chain. Nodes of various kinds are frequently used in more complex data structures.)
public class Node
{int datum;
node next;
node previous;
}
We could then declare an array
The present situation is shown in Figure 4B.2-2(a). Now let us allocate memory for 5 nodes:
new nodeArray[5];
So far this is done just as for an array of integers. However, the result is different, as there are no nodes yet, only a handle pointing to an array of 5 other handles, each of which points nowhere now but will point to a node as soon as we new one and assign the handle to it. Figure 4B.2-2(b) shows the present situation. Now we can allocate memory to the individual nodes:
for (int count=0; count++; count<=5) new nodearray[count];
After going through the loop twice, two of the handles in the array of nodes point to memory locations at which nodes are allocated, and the other three do not, as shown in Figure 4B.2-2(c). After the loop terminates, all five nodes are allocated, as shown in Figure 4B.2-2(d).
******************************************************** (a) After an array of nodes is declared but before memory is allocated for it. (b) The array after memory is allocated for 5 entries in it. (c) The array after memory is allocated for the first two nodes in it. (d) The array after memory is allocated for all 5 nodes in it. Figure 4B.2-2. An array of nodes at different stages of creation. ********************************************************Note that if an attempt is made to reference a node or array that has not yet been allocated, an error will result. Since the error is due to a pointer that has not been given a place to point to, the error is termed a "null pointer reference."
Passing simple and array arguments to methods
In the case of an argument that is a simple type (like int, char, etc.), a copy of the actual argument is made for the method at the time the method is called. Thus, if an integer variable someNum with value 9 is passed to a method through one of its arguments named intArg, at the moment the method is called a variable named intArg is created and the value of someNum, 9, is copied into intArg. Now there are two 9's where before there was only one. If the method now goes and changes the value of intArg to 8, the value of someNum is still 9. If the method wishes to return the value 8, then clearly changing the value of intArg is not the way to go since intArg will disappear when the method is finished running and the original variable passed to the method, someNum, will remain unchanged. The way for the method to return the 8 is to use a return statement.
Think, then proceed - what would memory look like right after intArg has been changed to 8? Draw a diagram.
Now let's see what happens when an array (or object, for that matter) is passed as an argument.
Since the array name really designates the handle of an array, not the array itself, the array valued argument of the method designates a copy of the handle, not a copy of the array itself, when the method is invoked, as shown in Figure 4B.2-3.
*********************************************************
public class DemoArrayArgs extends Applet
{
float data[]=new float[5];
void paint caller ()
{
calledMethod(data);
}
void calledMethod (float nums[])
{
.
. //Memory diagram when execution is here
. //is shown below.
}
}
Figure 4B.2-3. Code in which an array argument is passed (a),
and (b) the resulting memory diagram for the period after the method is
called and before it returns.
*********************************************************
When a copy of the actual argument is used locally, as when passing an
int argument, this is termed call by value. When a copy of the
handle is used locally, as when passing an array in Java, this is termed call
by reference. The most visible practical effect of call by value is that
changes to the argument are not visible outside the method, and disappear when
the method is finished, so that to return a value the method must use a
return statement. The most visible practical effect of call by
reference is that changes to the members of an array (or object) occur outside
the method and remain after the method finishes.
First, consider a 2-dimensional array. Once we understand them, it will be easy to extend the ideas to n-dimensional arrays. The basic concept behind a 2-dimensional array is that it is actually a 1-dimensional array of 1-dimensional array components. In other words, as you can have a 1-dimensional array of int, or a 1-dimensional array of objects of class TrafficLight or of class Node, you can just as easily have a 1-dimensional array of arrays. Those array components may themselves be ints, objects, etc.
Create a 2-dimensional array like this.
int twoDArray [][]; //Declares it as a handle for an array of arrays.
twoDArray = new int[5][10]; //Allocate an array of 5 unnamed handles for
//twoDArray to point to. Each points to an array
//of 10 integers.
twoDArray = new int[5][9]; //Discard the previous allocation and allocate
//a new array of 5 unnamed handles for
//twoDArray to point to. Each points to an array
//of 9 integers. The previously allocated memory
//that is now discarded may later be automatically
//reclaimed for other uses.
twoDArray = new int[5][]; //Allocate a new array of 5 unnamed handles for
//twoDArray to point to. Each handle does not
//yet point to anything, but will eventually
//point to an array of integers when allocated.
twoDArray[0] = new int[100];//The first element of twoDArray is a handle
//that is being set to point to a newly
//allocated array of 100 integers.
twoDArray[1] = new int[10]; //The second element of twoDArray is a handle
//that is being set to point to a newly
//allocated array of 10 integers.
//Note: twoDarray[2], for example, is a handle
//that does not yet point to anything.
Similarly, a 3-dimensional array is simply a 1-dimensional array of
2-dimensional arrays like twoDArray. More generally, an n-dimensional
array is a 1-dimensional array of (n-1)-dimensional arrays.
All arrays in Java are objects of the array class, a class which contains an automatically set member variable length which contains the number of elements in the array. This member variable is public, so to find out how many elements are in twoDArray, simply print out twoDArray.length, which will output a 5 in the example above.
Note to teachers: these problems may be suitable not only as homework exercises but as in-class exercises as well. They should help cement the concepts just presented and form a productive use of class time. Students could work individually in quiz-like fashion, or cooperatively in small groups. You may wish to circulate through the class, checking on their progress and providing assistance as needed.
For these problems, recall the code segment in an example presented earlier.
int twoDArray [][]; twoDArray = new int[5][10]; twoDArray = new int[5][9]; twoDArray = new int[5][]; twoDArray[0] = new int[100]; twoDArray[1] = new int[10];
1. Draw a memory diagram for right after the declaration int twoDArray [][];
2. Draw a memory diagram for right after the allocation statement twoDArray=new int[5][10];
3. Draw a memory diagram for right after the allocation statement twoDArray=new int[5][9]; make sure to depict both the previous allocation, now "adrift" without a handle, as well as the new allocation.
4. Draw a memory diagram for the allocation statement twoDArray=new int[5][]; do not depict any previous allocations - you may assume that the Java "garbage collector" has automatically deallocated them and made their memory available for other uses as needed (this is, in fact, exactly what the Java garbage collector does).
5. Draw a memory diagram for the state of the program after the allocation statement twoDArray[0]=new int[100]; you may assume the Java garbage collector has been working busily as in problem 4. Don't draw a sequence of 100 cells, but rather figure out a clear way to abbeviate this.
6. Draw a memory diagram for the state of the program after the allocation statement twoDArray[1]=new int[10]; use the assumption and abbreviation of problem 5.