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:
-
A variable is the name of a "location" that "stores" a value of a particular type.
-
For variables of primitive types, the value stored is the actual value of the type, e.g., an
intvariable stores what you should think of as a mathematical integer within some bounds. -
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
Stringvariable stores the value of a reference to an object whose value is a string of characters. -
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.
-
For example,
int i, j; i = 3; j = i; i = i + 1;results in the value of (primitive variable)
ibeing copied intoj(line 3) and when we increment the value ofi(line 4),iends up with the value 4 butjstill has value 3. -
On the other hand,
NaturalNumber n, m; n = new NaturalNumber2(3); m = n; n.increment();results in the value of (reference variable)
nbeing copied intom(line 3) and that means thatnandmare now referring to the sameNaturalNumberobject (i.e., they are aliases), and when we increment the value of the numbernrefers to (line 4),nends up referring to an object with the value 4 andmalso refers to the same object with value 4. -
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.
-
Parameter passing for primitive types:
Statement Variable Values private static void test1(int i) { i = i + 1; } Start tracing here int x = 7; test1(x); -
Parameter passing for (immutable) reference types:
Statement Variable Values private static void test2(String s) { s.toUpperCase(); } Start tracing here String str1 = "hello"; test2(str1); -
Parameter passing for (mutable) reference types:
Statement Variable Values private static void test3(NaturalNumber n) { n.increment(); } Start tracing here NaturalNumber num1 = new NaturalNumber2(17); test3(num1);
Setup
Follow these steps to set up a project for this lab.
- Create a new Eclipse project by copying
ProjectTemplate. Name the new projectParameterPassing. - Open the
srcfolder of this project and then open(default package). As a starting point you can use any of the Java files. Rename itParameterPassingand delete the other files from the project. - 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. - Finally in Eclipse, open the
ParameterPassing.javafile; 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.
-
The editor shows we are about to execute the statement
test1(x). -
The Variables view shows the variables in scope and, in particular, that
intvariablexhas value 7. -
The Console view displays the current output, including the value of
xbefore the call totest1(x). -
In the top left corner of the perspective, note the Debug view. This view contains some information that is beyond the scope of this lab, but note that the highlighted line tells us that we are on line 160 (in the screen shot; your line number may be different if you edited the program) of the
mainmethod in classParameterPassing. Soon we'll see how useful this view can be.
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 editor shows we are now in the body of
test1ready to execute its first (and only) statement. -
The Variables view shows the variables in scope inside
test1: there is only one and that is the parameteriwhose value, as expected, is 7. -
The highlighted line in the Debug view tells us that we are on line 29 (in the screen shot) of the
test1method in classParameterPassing.
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 execution, 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.
![Eclipse IDE debugging perspective showing a Java source editor and debugging panes. The editor highlights the line test1(x); as the next statement to execute. The Variables view shows an integer variable x with value 7. The Debug view displays a call stack including ParameterPassing.main(String[]). The Console pane shows program output including the line x = 7.](images/Screenshot010.jpg)
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.toUpperCase(); | ||
| } | ||
| Start tracing here | ||
| String str2 = "hello"; | ||
| test4(str2); | ||
| Statement | Variable Values | |
|---|---|---|
| private static void test5(String s) { | ||
| s = s + ", world!"; | ||
| } | ||
| Start tracing here | ||
| String str3 = "Hello"; | ||
| test5(str3); | ||
| Statement | Variable Values | |
|---|---|---|
| private static void test6(NaturalNumber n) { | ||
| n = new NaturalNumber2(n.toString() + "1"); | ||
| } | ||
| Start tracing here | ||
| NaturalNumber num2 = new NaturalNumber2("17"); | ||
| test6(num2); | ||
| Statement | Variable Values | |
|---|---|---|
| private static void swap1(int i1, int i2) { | ||
| int tmp = i1; | ||
| i1 = i2; | ||
| i2 = tmp; | ||
| } | ||
| Start tracing here | ||
| int x1 = 5, x2 = 8; | ||
| swap1(x1, x2); | ||
| Statement | Variable Values | |
|---|---|---|
| private static void swap2(String s1, String s2) { | ||
| String tmp = s1; | ||
| s1 = s2; | ||
| s2 = tmp; | ||
| } | ||
| Start tracing here | ||
| String str1 = "legends", str2 = "leaders"; | ||
| swap2(str1, str2); | ||
| Statement | Variable Values | |
|---|---|---|
| private static void swap3(NaturalNumber n1, NaturalNumber n2) { | ||
| NaturalNumber tmp = n1; | ||
| n1 = n2; | ||
| n2 = tmp; | ||
| } | ||
| Start tracing here | ||
| NaturalNumber num1 = new NaturalNumber2(43210), num2 = new NaturalNumber2(24601); | ||
| swap3(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.
-
Parameter passing for array types:
Statement Variable Values private static void test4(int[] a) { a[0] = a[0] + 1; } Start tracing here int[] array = { 1, 2, 3 }; test4(array); -
Swapping for array types:
Statement Variable Values private static void swap4(int[] a1, int[] a2) {
int[] tmp = a1;
a1 = a2;
a2 = tmp;
} Start tracing here int[] array1 = { 2, 2, 2, 1 };
int[] array2 = { 10, 2, 2012 };
swap4(array1, array2);