When you declare a variable of any type (including class/reference types), some space is reserved in memory in order to hold the value of the variable. The variable is just a symbolic name for this value in memory.
Let's say we declare a long.
long x;
This means that 8 bytes are reserved in memory and that x is the symbolic name for the value that is stored there.
When you declare a reference variable, the exact same thing happens.
Die d;
Since most JVMs are 64 bit, a reference variable declaration reserves 8 bytes (64 bits) in memory. This is because when an object is created, the object is created at some location in memory, and we need to be able to reference that location in memory. For example, let's examine the following statements:
Die d;
d = new Die();
The first line declares a reference variable for the Die class. This reserves 8 bytes and the variable name d is just a symbolic name for those 8 bytes. On the second line, we see that first a new Die object is created. The actual object is created somewhere else in memory. However, that place in memory still has an address. The "new" operator performs the construction of the object of a particular class (at some place in memory) and returns the address/location in memory where the object is being stored. It's this "reference" to the object's address in memory that is assigned to the reference variable d. Since 64 bit machines can address memory anywhere in the range of 0 to 2^8 -1, it's easy to see that the reason the reference variable is 8 bytes is because it needs to be able to store a value that is anywhere in this range.
The key here is to understand that value that the reference variable holds is the not the object that was created but a reference to the object's location in memory.
Let's examine the following snippet:
Die d1 = new Die();
Die d2 = d1;
We now know that the value stored in d1 is a reference to the location in memory of the newly constructed Die object. The second line of code takes the value of d1 (that reference) and assigns it to d2. You can think of this as both d1 and d2 pointing to the same object in memory. In this scenario, we call d1 and d2 aliases of each other.
When you invoke a method on d1, it used the object it's pointing to as the object the method operates with. For example:
d1.roll();
System.out.println(d1.getFaceValue());
The first statement above will invoke the roll method and modify the state of the object that d1 points to. The second statement will print the current face value of that object.
Since d2 points to the same object (it's value is also a reference to the location in memory where the same object is stored), when the following code is executed, the value that is printed to the screen will be the same as the previous snippet:
System.out.println(d2.getFaceValue());
If we do the following, a new object is created and a reference to the location in memory where it is stored is assigned to the value of d2:
d2 = new Die();
Now d1 and d2 each point to different objects.
Now comes the part about methods. Let's pretend we have the following method:
public int add(int a, int b) {
return a + b;
} // add
Let's also pretend that the following line somewhere else invokes the method:
int sum = add(3, 5);
We know that when that line gets executed that a couple different things happen. First, flow of control moved over to the method. Second, automatic local variables are declared for the method parameters. Third, the values that are passed into the method (i.e., 3 and 5) are assigned to the newly created local variables. It's as if the following snipped occurs as the first line of the method body:
// automatically declare local variables
int a, b;
// assign the value that's passed in
a = 3;
b = 5;
That makes sense since the fourth thing that happens is the method body is executed. In the method body, the automatic local variables are used. We know that variables cannot be used unless they've been declared. The imaginary snipped that I described above (that's inserted by the compiler) makes sure the variables in the method signature are declared before they are used in the method.
The thing that trips people up the most is when a method parameter is an object. Let's consider the following method:
public void rollMultiple(Die a, int b) {
for (int i = 0; i < b; i++) {
a.roll();
System.out.println(a.getFaceValue());
} // for
} // rollMultiple
Let's follow the same logic as before and imagine the following invocation of this method somewhere else in the code:
Die d = new Die();
rollMultiple(d, 3);
We know really well what happens the first line (explained above). In the second line, the flow of control moves into the method and we can imagine that following snipped of code (inserted by the compiler) occurs before the body of the method:
// automatically declare local variables
Die a;
int b;
// assign the values that are passed in
a = d;
b = 3;
When the body of the method is executed, both a (inside of the method) and d (outside of the method) are aliases of each other because the value that they hold is the same, a reference to the location in memory of an object.
Since a and d point to the same object, if we invoke a method on a, we can observe the change in the objects state in d. However, if you change the value of a by creating a new object and assigning a reference to that new object's location in memory to a, then invoking a method on a will not be observed in d. This is because changing the value of the reference variable a as described causes a and d to now point at different objects in memory.
Pretty simple right?
Now you probably have a firm grasp of why Java is known as "pass by value". It's only the values of parameters that are passed into the bodies of methods (via automatic local variable initialization).
In other languages, such as C and C++, it is possible to pass a variable to a method by reference. This allows someone to change the value of the original variables passed into the method. The easiest example is the following C code:
void doSomething(int& a) {
a = 2;
} // doSomething
Let's examine an invocation of the above method in C:
int b = 5;
doSomething(b);
printf(b)
The value that is printed to screen is 2!. This is because a reference to the primitive variable b
was passed into the method instead of the actual value in b
. The automatic variable a
is initialized to point to the same place in memory where the value for b
is stored. You can think of "pass by reference" as causing both variables to be symbolic names for the location in memory where a value is being stored. This caused the changes in the value of a (inside the method) to affect the changes in b (outside of the method). In Java, multiple variables cannot be symbolic names for the same location in memory where a value is being stored.
This does not happen in Java. In Java, references are never passed. Only values.
Hope that clears up some confusion!
Sincerely,
Michael E. Cotterell
Ph.D. Student in Computer Science, University of Georgia
Graduate RA & TA, University of Georgia
[email protected]
[email protected]
http://michaelcotterell.com/