Arrays as Objects


Now you know enough about arrays to write some very useful code. At this point, we will stop discussing the syntax and use of array code. It's time to look at what you might think of as array anatomy. This material is extremely important, because nearly everything you'll learn about array anatomy also applies to the anatomy of full-blown objects. If you understand the material in the remainder of this chapter, you will have a good solid foundation for learning about object-oriented programming in Java.

You have already seen that declaring an array is different from declaring a primitive. When you declare a primitive, the variable is right there for you. But when you declare an array, you still have to create it. This is a clue that there is more going on with arrays than with primitives.

You can think of memory as being divided into two parts: accessible and inaccessible. (This is unofficial terminology, but it is very useful and will be used throughout this book.) Primitives exist in accessible memory; arrays exist in inaccessible memory. Figure 6.6 shows memory divided into its two parts, populated with a few primitives and arrays.

click to expand
Figure 6.6: Accessible and inaccessible memory

Any variable on the left side of the figure – that is, in accessible memory – can have its values read and written. Accessible memory is for primitive variables, and for a kind of variable that you have already used without knowing it. This kind of variable is called a reference. References are used for all access to arrays. When you declare an array, what gets created is just a reference. (Remember, the array is not created until you say new.) The reference exists in accessible memory. No matter what kind of array you declare – no matter what type, size, or number of dimensions it has – the reference is 32 bits wide.

When you create an array by invoking the keyword new, space for the array is reserved (or allocated) in inaccessible memory. For example, the code int[][] ages = new int[3][2]; would cause allocation of 24 bytes (3 times 2 ints, times 4 bytes per int), as shown in Figure 6.7.

click to expand
Figure 6.7: An array of bytes in inaccessible memory

Invoking new is a bit like invoking a method: The code returns a value. The value returned by new is almost – but not quite – the address in inaccessible memory of the freshly created array. If you are at all unclear about the distinction between memory address and memory contents, you might want to return to Chapter 1 and play with the SimCom animated illustration.

Actually, it doesn't matter if the value returned by new is exactly the address of the array, or almost-but-not-quite the address, or only vaguely related to the address. The details of the relationship are a hidden part of the Java Virtual Machine, and they may even vary from one implementation of the JVM to another. For this reason, the value is called a reference to the array. Reference implies that the value uniquely identifies the array in a way that is hidden from us.

Now we can look at what really happens when the following code is executed:

int[][] ages;     // Allocation ages = new int[3][2];   // Construction & ref assignment 

The allocation line creates a reference named ages in accessible memory. Then the creation line causes space for the array to be allocated in inaccessible memory. The invocation of new returns a reference to the array; this reference is the right-hand side of the = assignment. The reference is then stored in the variable whose name appears on the left side of the = assignment, namely ages. This situation is illustrated in Figure 6.8.

click to expand
Figure 6.8: Reference and array

Notice that the comment on the second line of code above says Construction rather than Creation. Construction is the technical name for creating something in inaccessible memory by invoking new.

Why does this matter? So far, the array code we have presented has made sense without burdening you with all this reference stuff. But there are certain very useful operations you can do with arrays that only make sense if you understand references. These are operations that you have already seen in the context of primitives: assignment, and argument passing.

Suppose ages and otherAges are declared to be arrays of the same type. What does it mean to say the following?

ages = otherAges;

Contrary to reasonable expectation, this code emphatically does not create a new array whose components have the same values as the original array. Remember that ages and otherAges are really references. So ages = otherAges; just copies the 32-bit pattern from one reference to the other. The result is a second reference that (in some sense) points to the same thing the first reference pointed to: the array. You now have two references to the same array, as shown in Figure 6.9.

click to expand
Figure 6.9: Two references, one array

The CreateArrayLab animated illustration dynamically illustrates the following code:

double d = 1.23; double e = d; d = 3003; double[] doubleArray = new double[4]; double[] theCopy = doubleArray; doubleArray[1] = 98.6; e = theCopy[1]; 

Start the program by typing java arrays.CreateArrayLab. You will see the display shown in Figure 6.10.

click to expand
Figure 6.10: CreateArrayLab

Click on the Go button to see the animation. When the animation finishes and you want to watch it again, you can click on Reset to return the display to its original state.

References usually point to arrays (or to objects, as we will see later). However, there is a special value that can be assigned to any reference. The value is null, and it indicates that the reference does not point to anything. For example, you might make the following declaration and assignment:

double[] doubles; doubles = null;

The null reference value is only slightly useful at the moment, but you will see a use for it in the section on garbage collection later in this chapter. You will also make extensive use of null in later chapters, in the context of objects.

Passing References to Methods

Now you understand what really happens in code like the following:

float[] floatArray = new float[4]; float[] theCopy = floatArray;

Are you likely to encounter this situation? Are you likely to make a copy of a reference, when you already have a perfectly good one, given the confusion that a copy might create? Actually, yes. Within a single method, there isn't really any good reason for copying a reference. However, you might want to pass an array as a method argument.

Remember that when you pass a primitive as an argument to a method, the method actually gets a copy of the primitive. Thus, the method can modify the copy, and the caller will never be aware of the modification because the caller has no access to the modified copy.

With arrays and methods, the situation is a bit different. You don't actually pass an array into a method. You pass a reference to the array. The method receives a copy of the reference. The caller's original reference and the method's copy are identical 32-bit patterns, so they both (in some sense) point to the same object in inaccessible memory: the array. So when you pass an array reference as a method argument, the method can use the reference to modify the array, and the modifications will be visible to the caller.

The PassArrayLab animated illustration animates the following code:

int[] intarray = new int[3]; setInts(theArray);  . . . static void setInts(int[] ints) {   for (int n=0; n<ints.length; n++)     ints[n] = 22;   return; } 

The return statement isn't really required, since the method's type is void and it would return anyway after executing its last line. The return is just there to make the animation more clear. When a method returns, all variables that were declared within the scope of the method cease to exist. This includes the method's arguments (ints in this example). The space in accessible memory that was allocated for the variables is reclaimed by the system. Conceivably, the next variable to be declared could occupy the same bytes that used to constitute the ints argument. But all is not lost. Although the ints argument ceases to exist, the array it references continues to exist.

Invoke PassArrayLab by typing java arrays.PassArrayLab. The Go and Reset buttons start the animation and reset the display. Run the animation a few times, until you are confident that you understand that what gets passed to the method is a reference and not an array. That way, changes made to the array are permanent and visible to the caller.

Garbage Collection

Garbage collection is a mundane term for a very important feature.

You have seen that arrays are created by invoking the keyword new. Surely there must be a way to recycle an array's memory after the array is no longer needed. Something like this happens to the arguments and local data in a method, when the method returns. But in that case, the recycled data consists of primitives and references. In other words, it's data in accessible memory that was reserved by declaring arguments or variables. In the case of arrays, we are concerned with data in inaccessible memory that was reserved by invoking new.

Java's precursor languages, and in particular C and C++, required the programmer to explicitly free up memory that was no longer needed. This was the only way that the memory could become available for reuse. This led to problems. One such problem is called a memory leak bug. If a bug causes a method to neglect to free up a few hundred bytes, that's not much of a problem. The program is probably running on a system with at least several million bytes of available memory, so if a few hundred become unavailable, there is still plenty left. But if the method is called in a loop that executes 1,000 times, a few hundred thousand bytes become unavailable. That might have an impact. If the loop executes 1,000 times every hour, eventually there will be no more memory available for allocating new arrays, and the program will crash. The problem is called a memory leak because the pool of available memory gradually diminishes.

Java makes memory leaks highly unlikely, because in Java the programmer never decides when to recycle unneeded arrays and objects. The JVM decides when memory is no longer needed, and such memory is automatically recycled. The JVM uses the following logic to decide when to recycle memory:

When an array (and, as we'll see later, an object) is created, inaccessible memory is created and a reference is returned. The JVM keeps track of how many references are pointing to an array or other object. Your program might make copies of a reference, and might pass the reference as a method argument. As long as there is at least one reference to something, there is a chance that you might want to use that particular something, so its memory will not be recycled. However, when the last reference to an object ceases to exist, suddenly there is no way to read or write the object, or to use it in any way. You can't talk about something if you have no name for it. Since the unreferenced object can no longer play any role in your program, its memory will be automatically recycled. Such automatic recycling of unneeded memory is called garbage collection.

Consider the following method:

1. void useAnArray(int size) 2. { 3.   int[] theArray = new int[size]; 4.   int[] aRefCopy = theArray; 5.   int[] anotherCopy = theArray; 6. }

Line 3 constructs an array of ints and stores the returned reference in theArray. Line 4 copies the reference, so after line 4 there are 2 references to the array. After line 5, there are 3 references to the array, but only briefly. Immediately after line 5, the method returns, so all its variables are recycled. Suddenly, instead of 3 references to the array, there are none at all. Now the program no longer has any way to access the array, which will be recycled shortly.

If you have an array that you no longer need, there is no explicit way to recycle its memory. However, you can usually set all references to the array to null, which will cause garbage collection.




Ground-Up Java
Ground-Up Java
ISBN: 0782141900
EAN: 2147483647
Year: 2005
Pages: 157
Authors: Philip Heller

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net