Pass-by-value semantics in Java applications
A few months ago, developerWorks posted some excerpts from my book, Practical Java, published by Addison-Wesley. I will initially use this column on developerWorks to answer some questions asked by readers and to respond to various comments about the excerpts.
The excerpt Understanding that parameters are passed by value and not by reference described the one and only parameter-passing mechanism in Java applications, pass by value. It was written to debunk the commonly held myth that Java applications pass parameters by reference, to avoid common programming mistakes that result from relying on pass-by-reference behavior.
Some of the feedback on this excerpt suggested that I was confusing the issue or was altogether wrong about it. Many of the readers who disagreed with me used the C++ language as an example. Therefore, in this column, I will use C++ and Java applications to further clarify the facts.
The key point
After reading all of the comments, it became clear that there is at least one major point of confusion. Some comments said that my excerpt is wrong because objects are passed by reference. It is true that objects are passed by reference; the excerpt was not contradicting that. The excerpt said that all parameters are passed by value — a different argument. It is correct that you never pass an object in a Java application, only an object reference. Therefore, you are passing objects by reference. However, it is important to distinguish how parameters are passed, and that was the intent of the excerpt. The fact that a Java application passes objects by reference does not mean that a Java application passes parameters by reference. Parameters can be object references, and a Java application passes object references by value.
Parameter passing in C++ and Java applications
Variables in Java applications can be one of two types: reference types or primitive types. Both types are handled the same way when passed as arguments to a method. Both are passed by value; neither is passed by reference. This is an important distinction as the subsequent code examples illustrate.
Before going further, it is important to define the terms pass by value and pass by reference. Pass by value means that when an argument is passed to a function, the function receives a copy of the original value. Therefore, if the function modifies the parameter, only the copy is changed and the original value remains unchanged. Pass by reference means that when an argument is passed to a function, the function receives the memory address of the original value, not a copy of the value. Therefore, if the function modifies the parameter, the original value in the calling code is changed.
Some of the confusion about parameter passing in Java applications originates from the fact that many programmers came to Java programming from C++ programming. C++ contains both non-reference types and reference types and passes them by value and by reference, respectively. The Java programming language has primitive types and object references, and therefore, it is logical to think that Java applications use pass by value for the primitive types and pass by reference for references, much like C++. After all, you might think if you are passing a reference, it must be pass by reference. It is tempting to believe this, and in fact it was my belief for some time, but it is not true.
In both C++ and Java applications, when a parameter to a function is not a reference, you pass a copy of the value (pass by value). The difference is with references. In C++ when a parameter to a function is a reference, you are passing the reference, or the memory address (pass by reference). In Java applications, when an object reference is a parameter to a method, you are passing a copy of the reference (pass by value), not the reference itself. Note that the calling method’s object reference and the copy are pointing to the same object. This is an important distinction. A Java application does nothing differently when passing parameters of varying types like C++ does. Java applications pass all parameters by value, thus making copies of all parameters regardless of type.
Using the preceding definitions and discussion, we will analyze some examples. First consider some C++ code. The C++ language uses both pass-by-value and pass-by-reference argument passing mechanisms:Listing 1: C++ example
The output of this code is:
This code declares three variables: two ints and one pointer. Each variable is set to an initial value and then printed. The pointer value is printed along with what it points to. All three variables are then passed as parameters to the
modify function. The first two parameters are passed by value and the last parameter is passed by reference. The function prototype for the
modify function shows that the last parameter is to be passed as a reference. Recall that C++ passes all parameters by value, except references, which are passed by reference.
modify function changes the values of all three parameters:
- The first parameter is set to 0.
- The value the second parameter points to is set to 7, then the second parameter is set to 0.
- The third parameter is set to 0.
The new values are printed, then the function returns. When execution returns to
main, the values of the three variables, along with the value the pointer points to, are printed again. The variables passed as the first and second parameters are not affected by the
modify function because they were passed by value. The value that the pointer points to did change, however. Notice that unlike the first two parameters, the variable passed as the last parameter is changed by the
modify function because it is passed by reference.
Now consider similar code written in the Java language:
The output of this code is:
This code declares three variables: one int and two object references. Each variable is set to an initial value and printed. All three variables are then passed as parameters to the
modify method changes the values of all three parameters.
- The first parameter, the int, is set to 0.
- The first object reference, r1, is set to null.
- The second object reference, r2, retains its value but changes what it refers to by calling the
appendmethod. (This is analogous to what was done with the pointer, p, in the previous C++ example.)
When execution returns to
main, the values of the three variables are printed again. The int
val is unchanged as expected. The object reference
sb1 is also unchanged. If
sb1 was passed by reference, as many people claim, it would be null. However, since the Java programming language passes all parameters by value, a copy of the reference for
sb1 is passed to the
modify method. When the
modify method set
r1 to null at //1, it was doing so on a copy of the
sb1 reference, not the original as was done in C++.
Also, note that the second object reference,
sb2, prints out the new string that was set in the
modify method. Even though the variable
r2 in the
modify method is a copy of the reference
sb2, they both refer to the same object. Therefore, methods called on the copied reference change the same object.
Writing a swap method
Given what we know about how parameters are passed, writing a swap function in C++ can be accomplished in different ways. A swap function using pointers, which are passed by value, looks like this:Listing 5: Swap function using pointers
A swap function using references, which are passed by reference, looks like this:
Both C++ code examples swap the values as expected. If Java applications used pass by reference, the following swap method would work like the C++ examples:
Because the Java application passes all parameters by value, this code does not work and produces the following output:
So how do you write a method in a Java application to swap the values of two primitive types or two object references? Because a Java application passes all parameters by value, you cannot. To swap the values, you must do so inline, outside of a method call.