Method arguments
With an understanding of the difference between references and objects it is possible to understand how arguments are passed to methods, and more importantly, what happens when a method modifies one of these arguments. For instance, if a StringBuffer is passed to a method, and that method modifies it, does the caller of the method see the change to that StringBuffer, or does it retain a view to the StringBuffer’s original value?
Programming languages handle this possibility in two different ways, neither of which is inherently right or wrong:
• Pass by value: with this approach any changes made to an argument will not be seen by the caller. In the example mentioned, the caller would retain a view to the original StringBuffer, and the method would work with a copy of that object.
• Pass by reference: with this approach the caller will see any changes made to an argument inside a method when the method completes. With pass by reference, a reference to the object is passed, and therefore both parties are modifying the same underlying object when they use that reference.
Java does not easily fit into either of these categories.
When primitives are passed as arguments to a method, Java uses a pass by value approach. This means that the caller does not see any changes the method makes. This can be seen in the following example:
package parameters;
public class Primatives {
public static void main(String[] args) {
int num1 = 5;
System.out.println("Value at point 1 is "+num1);
changeMe(num1);
System.out.println("Value at point 3 is "+num1);
}
private static void changeMe(int num) {
num += 5;
System.out.println("Value at point 2 is "+num);
}
}
In this case, the method changeMe receives the value held by num1 (5). This is actually a copy of the value held by of num1, however, therefore any modification to this value inside changeMe will not be seen by the main method. This program therefore produces the following output:
Value at point 1 is 5
Value at point 2 is 10
Value at point 3 is 5
When a program passes an object reference to a method, however, things become more complex. Arguments are still passed by value, but to complicate matters, it is the reference to the object that is passed by value. This is an important distinction that even many experienced Java programmers do not fully understand. In order to understand the implications, consider the following example:
package parameters;
public class PassObject1 {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("Hello");
System.out.println("Value at point 1 is "+sb);
changeMe(sb);
System.out.println("Value at point 3 is "+sb);
}
private static void changeMe(StringBuffer sb1) {
sb1 = new StringBuffer("Hello World");
System.out.println("Value at point 2 is "+sb1);
}
}
This example behaves the same as the primitive-based example. A copy of the sb reference is passed to the changeMe method: the changeMe method then modifies which object on the heap this reference is referring to. Naturally, changing the value of this new object does not impact the original object. This can be seen in figure 11-3:
Figure 11-3
This program therefore prints:
Value at point 1 is Hello
Value at point 2 is Hello World
Value at point 3 is Hello
Make sure you fully understand this distinction. When the method is invoked, a copy of the original reference is created, and this copy refers to the same underlying object. Therefore, the memory addresses held by the two references are the same when changeMe is invoked.
Inside changeMe, the memory address of the copied references is changed so that it points to another object. This has absolutely no impact on the original reference held by the main method.
Compare this with the following program:
package parameters;
public class PassObject2 {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("Hello");
System.out.println("Value at point 1 is "+sb);
changeMe(sb);
System.out.println("Value at point 3 is "+sb);
}
private static void changeMe(StringBuffer sb1) {
sb1.append(" World");
System.out.println("Value at point 2 is "+sb1);
}
}
Rather than modifying the reference, the changeMe method is modifying the object that the sb1 parameter refers to, and this makes a big difference to the behavior of the program.
Remember, a copy of the sb reference from the main method has been passed to the changeMe method, but both the original and the copy are referring to the same object on the heap. As a result, the changeMe method now directly affects the value of the object created by the main method. As a result, the program prints the following:
Value at point 1 is Hello
Value at point 2 is Hello World
Value at point 3 is Hello World
This can be visualized as seen in figure 11-4:
Figure 11-4
I cannot stress enough how important it is that you understand these distinctions if you want to work effectively with Java. Take these examples and play around with them in Eclipse so you can assure yourself you understand what is happening in each case.
One final point worth emphasizing is the behavior of immutable objects such as Strings. Because there is no way that a method can modify the underlying value of an immutable object, this has an impact on the way argument passing works: specifically, it is not possible for the called method to alter the value of the object in a manner that will be reflected to the calling method:
package parameters;
public class PassObject3 {
public static void main(String[] args) {
String s = "Hello";
System.out.println("Value at point 1 is "+s);
changeMe(s);
System.out.println("Value at point 3 is "+s);
}
private static void changeMe(String s) {
s += " World";
System.out.println("Value at point 2 is "+s);
}
}
This produces the following result:
Value at point 1 is Hello
Value at point 2 is Hello World
Value at point 3 is Hello
The key to understanding this program is the following line:
s += " World";
Due to the fact that Strings are immutable, this line causes a new String object to be created: it does not change the value of the String originally created in the main method.