Lab: Parameter Passing


The Problem

This lab tackles a fundamental issue in Java programs: the difference between primitive and reference types and how assignment and parameter passing behave with each of these types. The short (and possibly still confusing) story is as follows:

  1. A variable is the name of a "location" that "stores" a value of a particular type.
  2. For variables of primitive types, the value stored is the actual value of the type, e.g., an int variable stores what you should think of as a mathematical integer within some bounds.
  3. For variables of reference types, the value stored is a reference (or a memory address referring) to an object whose value is the specified mathematical model of the type, e.g., a String variable stores the value of a reference to an object whose value is a string of characters.
  4. Both assignment and parameter passing in Java are consistent in that they copy the value stored in the variable being assigned from or the argument being passed to the method call -- which for primitive types is the actual value, but for reference types is the reference value rather than the object value.
  5. For example, results in the value of (primitive variable) i being copied into j (line 3) and when we increment the value of i (line 4), i ends up with the value 4 but j still has value 3.
  6. On the other hand, results in the value of (reference variable) n being copied into m (line 3) and that means that n and m are now referring to the same NaturalNumber object (i.e., they are aliases), and when we increment the value of the number n refers to (line 4), n ends up referring to an object with the value 4 and m also refers to the same object with value 4.
  7. Parameter passing exhibits a similar behavior because it involves the copying of the actual arguments into the formal parameters.

Consider the following tracing tables: each of them has a short method declaration followed by short client code that invokes the method. Carefully complete each tracing table starting from the client code and tracing over the method call and through the method body. Discuss them with the classmate sitting next to you and write down all the answers so that an instructor can check them out. If you encounter any problems or you have any doubts about any of the traces, please make sure you ask an instructor for help.

  1. Parameter passing for primitive types:

    Statement Variable Values
    private static void test1(int i) {
    i =
        i = i + 1;
    i =
    }
    Start tracing here
    int x = 7;
    x =
    test1(x);
    x =

  2. Parameter passing for (immutable) reference types:

    Statement Variable Values
    private static void test2(String s) {
    s →
        s.toUpperCase();
    s →
    }
    Start tracing here
    String str1 = "hello";
    str1 →
    test2(str1);
    str1 →

  3. Parameter passing for (mutable) reference types:

    Statement Variable Values
    private static void test3(NaturalNumber n) {
    n →
        n.increment();
    n →
    }
    Start tracing here
    NaturalNumber num1 = new NaturalNumber2(17);
    num1 →
    test3(num1);
    num1 →

Setup

Follow these steps to set up a project for this lab.

  1. Create a new Eclipse project by copying ProjectTemplate. Name the new project ParameterPassing.
  2. Open the src folder of this project and then open (default package). As a starting point you can use any of the Java files. Rename it ParameterPassing and delete the other files from the project.
  3. Follow the link to ParameterPassing.java, select all the code on that page (click and hold the left mouse button at the start of the program and drag the mouse to the end of the program) and copy it to the clipboard (right-click the mouse on the selection and choose Copy from the contextual pop-up menu), then come back to this page and continue with these instructions.
  4. Finally in Eclipse, open the ParameterPassing.java file; select all the code in the editor, right-click on it and select Paste from the contextual pop-up menu to replace the existing code with the code you copied in the previous step. Save your file.

Method

Run the program and compare the output for the first three test method calls (corresponding to the three tracing tables above). Does the observed behavior match the expected behavior from your tracing tables?

Let's use the Eclipse debugger to follow step-by-step the execution of the first three test methods. Set a breakpoint on the first line of the main method and start the program in the Eclipse debugger (see the debugging lab if you don't remember how to do that).

Single-step through the program until you reach the statement invoking test1 , but do not execute the method call yet. Here is what to look for in the debugger perspective at this point in the execution.

Stepping Into a Method Call

Now we want to track what happens to the parameter inside the test1 method body. Click on the Step Into button in the toolbar at the top of the perspective.

Note the following:

The beauty of the Eclipse debugging perspective (and all perspectives) is that all the views displayed are kept in sync. In the Debug view, click on the line ParameterPassing.main(String[])... and observe what happens in the Console and Variables views.

As you can see, they are updated to display the information about the state of the program back in the main method. If in the Debug view you click on the line ParameterPassing.test1(int)... , the various views revert back to showing the state of the program while inside the call to test1 . This is an extremely useful feature and one that we can leverage in this lab to keep track of whether values of variables change and when.

Parameter Passing of Primitive Types

At this stage in the excution, we can see that the value of the argument x passed to method test1 was copied into the formal parameter i and that's why the current value of i is 7.

Step over the assignment statement i = i + 1; in the body of test1 by clicking on the Step Over button in the toolbar. You can see in the Variables view that the value of i is updated to the value 8. However, now click on the line ParameterPassing.main(String[])... in the Debug view and observe that the value of x is still 7.

Let's get out of test1 . Click again on the line ParameterPassing.test1(int)... in the Debug view and then click on the Step Return button in the toolbar.

This takes us back to the main method just after the call to test1 . Note that the value of x is still 7, so it is clear that the value of the parameter ( i with value 8) has not been copied back into the actual argument. Click Step Over twice to complete the first test.

Parameter Passing of (Immutable) Reference Types

Let's step through the second test involving String s. Click Step Over three times to get to the call to the test2 method. Look at the Variables view and note that the String str1 has the value "hello" . However, because String is a reference type, Eclipse debugger displays also a numeric id that uniquely identifies a reference (in the screen shot, the id for str1 is id=25 ; the id you see may be different). This id allows us to recognize aliases (i.e., equal references) because aliased references will have the same id value.

Now let's step into the test2 method by clicking on the Step Into toolbar button. You can see that the Variables view is updated to display the only variable in the new scope which is the formal parameter s . Two things must be noted: first, the value of the String s is "hello" and second, the id for s is 25 (in the screen shots), the same as the id for the actual argument str . This shows that str and s are aliases, they refer to the same object whose value is "hello" .

Execute the statement in test2 's body by clicking the Step Over button. You might be surprised by what happens: nothing! The value of s does not change (the id is still the same and the object value is still the same). How is that possible? It turns out that String is an immutable type and that means that there are no methods in the String class that modify the value of the String they are called on. In particular, s.toUpperCase does not modify s ; it creates a new String object with the value of s converted to upper case and returns a reference to that object. The body of test2 simply ignores the value returned by s.toUpperCase (which is probably not something the programmer should do and that's why SpotBugs marks this statement with an appropriate warning).

You can complete this test by clicking Step Return to return to the main method and then clicking Step Over twice. It should not be a surprise that str was not changed by the method call.

Parameter Passing of (Mutable) Reference Types

Single-step through the next test ( test3 ) and use what you have learned about parameter passing, references, and the Eclipse debugger to see what happens and make sure you understand why. If needed, you should correct your trace and pay particular attention to those steps where the observed behavior differs from what you expected. If you have any question, make sure to ask your instructor.

More Traces

The program contains several more tests involving more examples of parameter passing. They are meant to help you understand this potentially confusing concept and they give you a chance to test your understanding.

For each of the following tracing tables, first complete the tracing table, and then single-step through the corresponding test to see whether the code behaves as you expected. Any time the observed behavior does not match your expectations, you should stop and try to understand where your trace went wrong. Make sure to ask your instructor for help if you cannot figure it out on your own.

Statement Variable Values
private static void test4(String s) {
s →
    s = s.toUpperCase();
s →
}
Start tracing here
String str2 = "hello";
str2 →
test4(str2);
str2 →

Statement Variable Values
private static void test5(String s) {
s →
    s = s + ", world!";
s →
}
Start tracing here
String str3 = "Hello";
str3 →
test5(str3);
str3 →

Statement Variable Values
private static void test6(NaturalNumber n) {
n →
    n = new NaturalNumber2(n.toString() + "1");
n →
}
Start tracing here
NaturalNumber num2 = new NaturalNumber2("17");
num2 →
test6(num2);
num2 →

Statement Variable Values
private static void swap1(int i1, int i2) {
i1 =
i2 =
    int tmp = i1;
i1 =
i2 =
tmp =
    i1 = i2;
i1 =
i2 =
tmp =
    i2 = tmp;
i1 =
i2 =
tmp =
}
Start tracing here
int x1 = 5, x2 = 8;
x1 =
x2 =
swap1(x1, x2);
x1 =
x2 =

Statement Variable Values
private static void swap2(String s1, String s2) {
s1 →
s2 →
    String tmp = s1;
s1, tmp →
s2 →
    s1 = s2;
s1, s2 →
tmp →
    s2 = tmp;
s1 →
s2, tmp →
}
Start tracing here
String str1 = "legends", str2 = "leaders";
str1 →
str2 →
swap2(str1, str2);
str1 →
str2 →

Statement Variable Values
private static void swap3(NaturalNumber n1, NaturalNumber n2) {
n1 →
n2 →
    NaturalNumber tmp = n1;
n1, tmp →
n2 →
    n1 = n2;
n1, n2 →
tmp →
    n2 = tmp;
n1 →
n2, tmp →
}
Start tracing here
NaturalNumber num1 = new NaturalNumber2(43210), num2 = new NaturalNumber2(24601);
num1 →
num2 →
swap3(num1, num2);
num1 →
num2 →

Additional Activities

For each of the following tracing tables involving arrays, first complete the tracing table, and then single-step through the corresponding test to see whether the code behaves as you expected. Any time the observed behavior does not match your expectations, you should stop and try to understand where your trace went wrong.

  1. Parameter passing for array types:

    Statement Variable Values
    private static void test4(int[] a) {
    a →
        a[0] = a[0] + 1;
    a →
    }
    Start tracing here
    int[] array = { 1, 2, 3 };
    array →
    test4(array);
    array →

  2. Swapping for array types:

    Statement Variable Values
    private static void swap4(int[] a1, int[] a2) {
    a1 →
    a2 →
        int[] tmp = a1;
    a1, tmp →
    a2 →
        a1 = a2;
    a1, a2 →
    tmp →
        a2 = tmp;
    a1 →
    a2, tmp →
    }
    Start tracing here
    int[] array1 = { 2, 2, 2, 1 };
    int[] array2 = { 10, 2, 2012 };
    array1 →
    array2 →
    swap4(array1, array2);
    array1 →
    array2 →