Parameter passing

Lesson content

  • The concept of parameter passing
  • Passing method parameters by value and by reference
  • Most common errors

The concept of parameter passing

What is (method) parameter passing? It was already explained that a method has:

  • Formal parameters (stated as part of the method’s header)
  • Real parameters (arguments - real values passed on to the method upon method call)

Parameter passing is, commonly speaking, the way real parameters (arguments) are passed to the method upon calling the method to execute.

Why is this important? Because some programing languages automatically make copies of argument values and pass those copies to methods (thus making the “original” value intact even if the copied value changes during method execution), while others support passing the “original”, so it can be altered during method execution. Some languages support both options.

Passing method parameters by value and by reference

As stated, in programming languages, there are usually at least two types of ways to pass parameters to methods:

  • Passing parameters by value (make and pass copy)
  • Passing parameters by reference (pass original)

In the Java programming language, all parameters are passed by value (formally speaking), but this means different things depending on whether the parameter is of a primitive type or not. This can lead to confusion, making it seem like changes made to a parameter won’t affect the original value. This is explained in more detail in the following examples.

When primitive type arguments are passed to a method, the parameter is passed by value in the “expected” way. This means that the value of the argument is copied. Each time a parameter is referenced inside the method’s body, it is actually a copy of the passed argument. Therefore, changes made to the parameter during the method’s execution will not persist (since changes are made to the copy, not the original). After the method finishes, the copy is discarded.

Example 1

Let’s consider the class ParameterPassing, which has two methods:

  • The method calculateSquare takes an integer as a parameter, squares it (modifies the parameter itself), and prints the result to the screen.
  • The method swapNumbers takes two integers as parameters and tries to swap their values.
 1     class ParameterPassing {
 2 
 3         void calculateSquare(int number) {
 4             number = number * number;
 5 
 6             System.out.println("It's square is: " + number);
 7         }
 8 
 9         void swapNumbers(int a, int b) {
10             int pom = a;
11             a = b;
12             b = pom;
13         }
14     }

And, let’s consider the TestParameterPassing class which calls both methods of the ParameterPassing class and prints the results to the screen.

 1 class TestParameterPassing {
 2 
 3     public static void main(String[] args) {
 4         ParameterPassing pp = new ParameterPassing();
 5 
 6         int k = 13;
 7 
 8         System.out.println("The number is: " + k);
 9 
10         pp.calculateSquare(k);
11 
12         System.out.println("After squaring, the number is: " + k);
13 
14         int x = 22;
15         int y = 15;
16 
17         System.out.println("Numbers before the swap: " + x + " " + y);
18 
19         pp.swapNumbers(x, y);
20 
21         System.out.println("Numbers after the swap: " + x + " " + y);
22     }
23 }

When the program runs, the following values will be printed on the screen (the initial values of the variables k, x, and y remain unchanged):

1 The number is: 13
2 It's square is: 169
3 After squaring, the number is: 13
4 Numbers before the switch: 22 15
5 Numbers after the switch: 22 15

It can be seen that the variable k has the same value before and after squaring, even though it was passed to the calculateSquare method. Also, neither the variables x nor y have swapped values after calling the second method. The reason is that these values were passed to the method as primitive parameters, i.e., they were passed by value.

What does passing a parameter by value actually mean, and how does it look in the computer’s memory (Figure 16)?

Passing parameters by value (primitive types)
Figure 16. Passing parameters by value (primitive types)

When the calculateSquare method is called, the value of the variable k, which is 13, is passed to the calculateSquare method as an argument. This means a new local variable number is created in the calculateSquare method, and the value 13 is stored in it — this is a copy. The squaring operation inside this method only changes the value of the variable number (the copy of the number 13), while the variable k keeps the same value. When the square is printed, the value of number, which is 169, is printed. Once the method finishes, all its local variables are discarded, including the number variable. Finally, when the value of k is printed after the square method finishes, it prints 13.

Something similar happens when the swapNumbers method is called (Figure 17). The values of the variables x and y, i.e., the numbers 22 and 15, are passed to the swapNumbers method as arguments for the formal parameters a and b. This means that new local variables a and b are created in the swapNumbers method, and the values 22 and 15 are stored in them — these are copies. Then, a new local variable temp is introduced to perform the swap. Swapping the values inside this method only changes the values of the variables a and b, while the variables x and y retain their original values. When the method execution finishes, all its local variables are discarded — variables a, b and temp. This means that the changes to the numbers are not retained. When the values of x and y are printed after the swapNumbers method finishes execution, the original values of 22 and 15 are printed (not 15 and 22 as one might expect).

Passing parameters by value (primitive types)
Figure 17. Passing parameters by value (primitive types)

If an object (a complex type) is passed to a method as an argument, the parameter is still passed by value. However, the value of the parameter in this case is the object’s address, so it would be more accurate to say that the parameter is passed by reference.

Here’s the explanation. Passing an object as an argument is done by specifying the corresponding pointer to the object — the variable. In this case, the argument’s value is copied, and since it’s a pointer, the memory address of the original object is copied. When the parameter is referenced inside the method, it accesses the object at the address that was previously copied, which is where the original object is.

The consequence of this approach is that changes made to the object passed as an argument during the method’s execution are retained after the method finishes (such as changes to the values of the object’s attributes, its relations, or calling its methods). However, changes made to the address of the passed object (within the method’s body) won’t be retained after the method finishes (such as creating a new object or replacing the address with null or another value) because the changes are made to a copy of the address, and the original pointer still holds the original address.

Example 2

Let’s consider the class Person, which has the attributes height (integer) and weight (floating-point number).

1 class Person {
2     int height = 0;
3     double weight = 0;
4 }

Let’s also consider the class ParameterPassing2, which has three methods:

  • The first method takes a Person object as a parameter and adds 5 kilograms to the person’s existing weight (modifies the weight attribute).
  • The second method takes two Person objects as parameters and swaps their places by swapping the addresses of the passed objects.
  • The third method takes a Person object as a parameter and creates a new object at a new address.
 1     class ParameterPassing2 {
 2 
 3         void addWeight(Person person) {
 4             person.weight = person.weight + 5;
 5         }
 6 
 7         void swapPersons(Person person1, Person person2) {
 8             Person tmp = person1;
 9             person1 = person2;
10             person2 = tmp;
11         }
12 
13         void makeNewPerson(Person newPerson) {
14             newPerson = new Person();
15         }
16     }

The TestParameterPassing2 class calls all three methods from ParameterPassing2 and prints the results to the screen.

 1 class TestParameterPassing2 {
 2 
 3     public static void main(String[] args) {
 4         Person p1 = new Person();
 5         p1.height = 196;
 6         p1.weight = 95;
 7 
 8         Person p2 = new Person();
 9         p2.height = 172;
10         p2.weight = 55;
11 
12         ParameterPassing2 pp2 = new ParameterPassing2();
13 
14         System.out.println("Person with initial height and weight: "+ 
15         p1.height+"cm "+ p1.weight+"kg");
16 
17         pp2.addWeight(p1);
18 
19         System.out.println("Person with final height and weight: "+ 
20         p1.height+"cm "+ p1.weight+"kg");
21 
22         System.out.println("Persons before swapping:");
23         System.out.println("First person: "+ p1.height+"cm "+
24          p1.weight+"kg");
25         System.out.println("Second person: "+p2.height+"cm "+
26         p2.weight+"kg");
27 
28         pp2.swapPersons(p1, p2);
29 
30         System.out.println("Persons after swapping:");
31         System.out.println("First person: "+ p1.height+"cm "+
32          p1.weight+"kg");
33         System.out.println("Second person: "+p2.height+"cm "+
34         p2.weight+"kg");
35 
36         pp2.makeNewPerson(p2);
37         System.out.println("New person: "+ p2.height+"cm "
38         +p2.weight+"kg");
39     }
40 }

When the main method of the TestParameterPassing2 class is run, the following will be printed:

1 Person with initial height and weight: 196cm 95.0kg
2 Person with final height and weight: 196cm 100.0kg
3 Persons before swapping:
4 First person: 196cm 100.0kg
5 Second person: 172cm 55.0kg
6 Persons after swapping:
7 First person: 196cm 100.0kg
8 Second person: 172cm 55.0kg
9 New person: 172cm 55.0kg

The addWeight method works as expected because the person’s weight has truly been permanently changed from 95 to 100 kilograms.

Then, we see that the persons were not swapped after executing the swapPersons method. The reason is that the addresses of the entire objects cannot be swapped.

Finally, the new object created by the makeNewPerson method was not actually passed back to the variable p2 — the values for weight and height remain the same and are not changed to 0 cm and 0 kg (which would be expected for a new Person object - the default values for its int and double attributes height and weight).

What does passing a parameter by reference actually mean, and how does it look in the computer’s memory?

Based on the previous example, we can see the following (Figure 18). When the addWeight method is called and the variable p1 is passed as a parameter, the address from this variable (e.g., 450) is copied to the new local variable person in this method. The object itself is not copied; there are now two pointers pointing to the same object in memory (p1 and person). When the addWeight method is executed, it accesses the address passed (the original object) and changes the value of its weight attribute from 95 to 100. After the addWeight method finishes, the local variable person is discarded, but the object at address 450 is not deleted (since p1 still points to it). Finally, p1 retains the address 450, pointing to the same object, which now has the updated weight. When the method for printing is called, it shows that the person has a height of 196 cm and a weight of 100 kg.

Passing parameters by reference (complex types)
Figure 18. Passing parameters by reference (complex types)

A similar thing happens during the execution of the swapPersons method, but the objects will not swap places because the addresses of the passed objects cannot be swapped (Figure 19). When the method is called, the values of the parameters p1 and p2 (addresses 450 and 335) are first copied to the new local variables person1 and person2. Then, the addresses of person1 and person2 are swapped using the new local variable temp. After this swap, only the variables person1 and person2 have swapped addresses (now 335 and 450, respectively), but the variables p1 and p2 still hold their initial addresses (450 and 335). When the swapPersons method finishes, the local variables person1, person2, and temp are discarded, but p1 and p2 retain their original addresses, so the effect of this method is invalid. The objects at addresses 450 and 335 are not deleted because p1 and p2 still point to them, and the screen output will show the unchanged details for both persons.

Passing parameters by reference (complex types)
Figure 19. Passing parameters by reference (complex types)

Finally, when the makeNewPerson method is executed, a new object is created (address 620). This new object is passed to the local variable newPerson in the method, but it does not replace the reference from the p1 variable, since p1 still points to the original address 450. Therefore, the new object is discarded once the method ends, and the output on the screen does not show any changes to p1.

Memory overview (complex types)
Figure 20. Memory overview (complex types)

Finally, if the methods swapPersons and makeNewPerson should be rewritten to behave as expected, they would need to swap the values of the attributes of the input objects (first method) and return the address of the new object as the method’s return value, which should then be stored in the initial variable, while removing the parameter (second method). The modified code could look like this:

 1 void switchPersons(Person person1, Person person2) {
 2     Person tmp = new Person();
 3     
 4     tmp.height = person1.height;
 5     tmp.weight = person1.weight;
 6         
 7     person1.height = person2.height;
 8     person1.weight = person2.weight;
 9         
10     person2.height = tmp.height;
11     person2.weight = tmp.weight;
12 }
13 
14 Person makeNewPerson() {
15     return new Person();
16 }

Most common errors

Common mistakes related to parameter passing in methods that are not syntax errors, but affect the program’s behavior are:

  • Passing a primitive type variable as a parameter to a method and expecting the method to change the value of the passed variable. Since it’s working with a copy, the value remains unchanged.
  • Passing a complex type (object) variable as a parameter to a method and expecting that the method will not change the attribute values of the passed variable. Since it’s working with the original object through its address, the values of its attributes will be modified.
  • Passing a complex type (object) variable as a parameter to a method and expecting that a new object will be initialized within that variable. Since it’s working with the original object through its address, the initial variable (which was passed to the method) will still hold the address of the original object.