|
|
It's common to use the term reference variable for any variable that holds a reference to dynamically allocated storage for a class instance, for example, fido in the following code:
Dog fido = new Dog();
In reality, all variables in high-level languages provide a symbolic reference to a low-level data storage area. Consider the following code:
int x;
Dog fido;
Each of these variables represents a data storage area that can hold one scalar value. Using x we can store an integer value such as 5 for subsequent retrieval (reference). Using fido we can store a data value that is the low-level address (in memory) of a dynamically allocated instance of a user-defined data type. The critical point is that, in both cases, the variable "holds" a scalar value. In both cases, we can use an assignment operation to store a data value:
int x = 5; // 1.
int y = x; // 2. x's value also stored in y
Dog fido = new Dog(); // 3.
Dog myDog = fido; // 4. fido's value also stored
// in myDog
Dog spot = null; // 5.
In the second line, y is initialized with the current value of x. In the fourth line, myDog is initialized with the current value of fido. Note, however, that the value in fido is not the instance of Dog; it is the Java interpreter's "recollection" of where (in memory) it stored the instance of Dog. Thus, we can use either reference variable to access this one instance of Dog. With objects, the context in which we use the variable determines whether it simply evaluates to the memory address of an object or actually initiates more powerful operations. When the usage involves dot notation, for example, fido.bark(), the evaluation includes binding the object to the appropriate method from the class definition, that is, invoking a method and performing the implied actions. But, when the usage is something like "... = fido;", the evaluation is simply the address. Consider the expression evaluations that take place within the parentheses in the following code:
String sound = "Woof."; // 1.
fido.bark(sound); // 2. void bark(String barkSound) {...}
int numberBarks = 4; // 3.
fido.bark(numberBarks); // 4. void bark(int times) {...}
In the first line, the String instance "Woof." is dynamically allocated in storage and its location/address stored in sound. In the second line, the evaluation of the argument to bark() is simply the scalar value (memory address) stored in the sound reference variable because it is more logical to pass along a scalar value than to make another copy of the string instance. That is, the argument is a copy of the scalar value held in sound. In the fourth line, the evaluation of the argument to bark() is simply the scalar value stored in numberBarks. In both cases, the data passed as arguments agree in type with the respective parameters in the method definitions. And, in both cases, the method invocation involves making a copy of the value and passing the copy forward. In the latter case, this process is generally called call by value because the invoked method receives a copy of the ultimate value (4) from the int variable numberBarks. When the argument and parameter types are nonprimitive (a defined class), this process is generally called call by reference because the invoked method receives a copy of a reference value. Consider the implication for how the parameters are used in the invoked methods. In the latter case, the int parameter times is actually modified (decremented) in the method:
void bark(int times) {
while (times-- > 0)
System.out.println(barkSound);
}
This modification does not, of course, affect numberBarks, which exists in a different context (in the invoking method main()), because this method receives a copy of the value in numberBarks. In the former case, the String parameter barkSound is evaluated as the argument to println(), but because it is a reference variable, the scalar value is, once again, copied and passed forward in the method invocation chain:
void bark(String barkSound) {
System.out.println(barkSound);
}
This evaluation of a reference variable is consistent with a previous example from the panel Strings:
Dog bruno = new Dog();
...
System.out.println(bruno);
In this case, the expression evaluation for the argument to println() is a reference variable alone (no dot notation), so its scalar value is copied and passed alone. In both cases, the context, that is, the ultimate evaluation within println(), requires automatic conversion to a string for display. Ultimately, within one of the println() methods (actually, after yet another round of invocations in the case of bruno), dot notation is finally applied to the object, in effect, "<reference-variable>.toString()". There is one important ramification of call-by-reference argument passing. If a method received a reference to an object, it can potentially modify the state of that object:
class Person {
...
void walkDog(Dog dog) {
if (dog.barksAtEverything() && dog.tugsAtLeash())
dog.setGentle(false);
}
...
}
Therefore, in designing classes the burden is on the class designer to control which state variables can be modified, and by whom. copyright 1996-2000 Magelang Institute dba jGuru |
|