Writing Loops Inside Subroutines
A parameterized subroutine declares a parameter list in its definition, which specifies the number and types of values a caller must supply.
A Subroutine You Can Run
This article starts with a subroutine rather than a list of rules. The subroutine prints a 3N+1 sequence. The number the sequence starts from is not written inside the subroutine. Instead, the caller supplies that number each time it calls the subroutine. As you read, keep one question in mind: when the subroutine runs, where does its starting number come from, and at what point does it first have a value?
What do you think happens?
Inside the subroutine, where does the starting number come from?
Reveal answer
The caller supplies it in the call statement
The subroutine's definition declares a parameter but does not give it a value. The number the subroutine starts from is the value the caller places inside the parentheses of the call.
Why Subroutines Get Called More Than Once
Imagine a program that needs the same job done in three different places. You could copy the same lines of code three times, or you could write those lines once and let the rest of the program use them whenever they are needed. A subroutine is the second option. It is a block of code that can be called multiple times from different parts of a program.
The important word is called. A subroutine does not run just because it exists in a program. It runs when the program reaches a point where it calls it. Because the same block can be called more than once, a single piece of code can serve many moments in a single run of the program.
Reading a Parameter List
The subroutine in this article is declared with a parameter list in its header. The header is static void print3NSequence(int startingValue). The part in parentheses is the parameter list. It says that the subroutine expects exactly one parameter, and that the parameter is of type int. The number and types of the parameters in this list tell any caller what must be supplied.
Read the header from left to right to find what a caller needs. The name tells the caller which subroutine to invoke. The type in the parameter list, int, tells the caller what kind of value must be supplied inside the parentheses. The parameter name, startingValue, is the name the body will use for that value. The caller does not need to know this name to make the call, because the value is passed in by position.
How a Call Value Reaches the Parameter
The definition does not give the parameter a value. The value comes from the call statement. When the computer executes a subroutine call statement, it first assigns the value supplied in the call to the parameter name, and only then executes the statements of the subroutine body. The parameter name can then be used inside the body in the same way as a variable name.
The timing is the key point. The assignment to the parameter happens before the first statement of the body, which is why the body can rely on the parameter already holding a value. If you want to know what the body sees on its first line, the answer is always the value the call supplied.
Tracing a Call That Uses a Variable
Tracing print3NSequence(K) step by step
Given the driver above, what value does the body use as its starting point when the second call runs?
- 1Read the caller's variable
The call is
print3NSequence(K). The computer takes the current value of K, which is 5. - 2Assign to the parameter
The value 5 is assigned to
startingValue. The variable K is the source of the value;startingValueis the name the body uses. - 3Run the body
Only after the assignment does the body execute, and it prints the sequence beginning at 5.
The body begins from 5, because the value of K was copied into startingValue before the first statement of the body ran.
What do you think happens?
Inside the body, can the code refer to the caller's name K directly?
Reveal answer
No, the body refers to the value through startingValue
The body is written in terms of startingValue, so it has no direct connection to the name K in the caller. Any variable in the caller could be passed, as long as its current value is what the call should use.
Tracing Several Calls From the Main Program
A main program can call the same subroutine at several points in its run. Each call is a separate event. Control leaves the main program, enters the subroutine, runs the body with that call's value, and then returns to the point where the call was made. The sequence below uses the two calls from the driver plus one additional call with a different value to show the pattern.
Notice that each call starts the body fresh with its own supplied value. The first call supplies 17, the second supplies the current value of K, and the third supplies 12. The same definition is used every time, and only the value assigned to startingValue changes between calls.
Finding Where a Wrong Result Begins
When a parameterized subroutine produces a wrong result, you can narrow down the cause by following one path in order. First, identify the value supplied by the call. Second, check its assignment to the parameter. Third, check the body's first use of that parameter. The step where the value first differs from what you expected is where the problem begins.
Subroutines in ThreeN2.java
The source points to ThreeN2.java as a concrete program that uses subroutines. It is introduced as an improved version of the 3N+1 program, and the source describes it as a program that uses subroutines and prints its output in neat columns. The source does not list the individual subroutines inside ThreeN2.java or show their code, so this article does not describe them in detail.
The source gives two reasons ThreeN2.java relies on subroutines. The first is code organization: separating work into named blocks makes the program easier to read and manage. The second is output formatting: the program prints its results in neat columns, and the subroutines are part of how that formatting is achieved. When you open the file yourself, look for the places where the program calls a named block of code, and compare them with the places where it prints results.
Mistakes Beginners Make With Parameters
Common mistake Thinking the parameter list gives the parameter a value
What it looks likeReading the header static void print3NSequence(int startingValue) and assuming startingValue already holds a number.
Why it failsThe definition declares the parameter but does not give it a value. The value arrives from the call statement.
Do this insteadLook at the call statement to find the value, then follow it to the parameter.
Common mistake Expecting the body to use the caller's variable name
What it looks likeAssuming the body refers to K when the call is print3NSequence(K).
Why it failsThe body is written in terms of
startingValue. The caller's variable is only the source of the value.Do this insteadTrack the current value of K at the moment of the call, then follow it into startingValue.
Common mistake Assuming a subroutine runs just because it is written
What it looks likeWriting the subroutine and expecting its output to appear without any call.
Why it failsA subroutine runs when the program reaches a point where it calls it.
Do this insteadFind the call statement that names the subroutine to see when it runs.
Common mistake Forgetting that each call supplies its own value
Practice Tracing Calls
A program contains the header static void print3NSequence(int startingValue) and the driver lines int K = 8; print3NSequence(K); K = 3; print3NSequence(17);. For each call, state the value assigned to startingValue before the body runs. Then explain whether the second call uses 8 or 3 when the body executes its first line.
Hints
- Find the value of K at the exact moment each call statement runs, not when the variable was first declared.
- The body only sees
startingValue, so ask what value was assigned to that parameter for each call.
A driver calls print3NSequence(K) three times, and the second call prints a sequence that starts from an unexpected number. Describe the three steps you would check, in order, to locate the step where the value first diverges.
Hints
- Start with the value supplied by the call.
- Then check the assignment to
startingValueand the body's first use of it.
Key Takeaways
- A parameterized subroutine declares a parameter list in its definition, which tells a caller how many values to supply and of what types.
- The definition does not give the parameter a value; the call statement supplies it, and the assignment happens before the body's first statement runs.
- A subroutine runs only when a call statement reaches it, so one named block can serve many points in a single program run, each with its own supplied value.
- When a result is wrong, trace the supplied value, its assignment to the parameter, and the body's first use of it, in that order, to find the step where the value first diverges.
ThreeN2.javauses subroutines to organize its code and to help format its output in neat columns.