Writing Subroutines with Parameters
A subroutine's signature is its name together with the number and types of its formal parameters, written as subroutine_name(type1,type2,...).
Calling the Same Job from Many Places
Imagine a program that needs to print a number, print a piece of text, and print two numbers side by side. You could invent three different names for these jobs, or you could reuse one name and let the situation decide which job actually runs. Java supports the second approach. This article shows what makes that choice possible, how the language keeps the versions apart, and how you can predict which version will run when you write a call.
Before reading on, pause and ask yourself: if a program needs the same calculation or the same printed output in several places, where would you rather keep the instructions for it, in one place or in every place it is needed? The main idea of subroutines is an answer to that question, and the rest of this article builds on it.
What a Subroutine Is
A subroutine is a block of code that can be called multiple times from different parts of a program.
Suppose a program needs the same job done in three different places. One option is to copy the same lines of code three times. The other option is to write those lines once and let the rest of the program use them whenever they are needed. A subroutine is the second option. The important word in the definition 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.
Tracing One Subroutine Called Twice
Follow the flow of control in a run where a main program calls the same subroutine twice. The subroutine here is showLabel, which takes a name as text and a count as a whole number, so its signature is showLabel(String,int). The diagram shows the order of messages between main and showLabel.
Notice that the subroutine body is written only once, yet it executes twice. Each time control reaches the call, the program jumps into the subroutine, runs its instructions, and then returns to the statement that follows the call in main. The same stored instructions serve both calls, and the arguments supplied at each call are what differ between the two runs.
Reading a Signature
Before you can call a subroutine legally, you need three pieces of information: its name, how many formal parameters it has, and the type of each of those parameters. Taken together, these make up the subroutine's signature. A convenient way to write a signature is the subroutine name followed by the parameter types in order, separated by commas. For example, doTask(int,double,boolean) has the name doTask, three parameters, and the types int, double, and boolean, in that order.
Writing the signature of showLabel
A subroutine takes a name as text and a count as a whole number. What is its signature?
- 1Find the name
The subroutine is called
showLabel. - 2Count the parameters
There are two formal parameters: one for the name and one for the count.
- 3List the types in order
The first parameter is text, which is a String, and the second is a whole number, which is an int.
- 4Assemble the signature
Write the name, then the types in order, separated by a comma:
showLabel(String,int).
The signature is showLabel(String,int). The name showLabel, the two parameter positions, and the two types together identify the subroutine. Nothing else in that description is needed to call it.
What the Signature Leaves Out
A signature leaves out the names of the parameters. Suppose doTask has a parameter called count in its definition. A caller who only wants to use doTask does not need to know that the parameter is called count. The caller only needs to supply a whole number in that position. Because someone using a subroutine does not need the parameter names, those names are treated as part of the subroutine's internal wording rather than part of its interface.
The signature therefore describes the outside view of the subroutine: what a caller must supply, and nothing more. The statements inside the body are not part of it. Under the definition used here, the signature is the name together with the number and types of the formal parameters, so the body and the parameter names stay on the inside.
Overloading: One Name, Several Signatures
Java is somewhat unusual in allowing two different subroutines in the same class to share a name, as long as their signatures differ. When this happens, the name is said to be overloaded, because it now has several meanings. The condition is specific. The name must match, the signatures must differ, and both subroutines must belong to the same class. Two subroutines with the same name and identical signatures would be indistinguishable to the computer, so that combination is not allowed to coexist.
The source points to a familiar case. The System.out object has several methods named println, and they all have different signatures. You have been using overloading every time you wrote a println call, even if you did not have a name for it.
| Feature | Overloaded subroutines | Duplicate subroutines |
|---|---|---|
| Name | Same | Same |
| Number and types of parameters | Differ | Identical |
| Allowed in the same class | Yes | No, the computer cannot tell them apart |
| Which one runs | Chosen from the actual parameters in each call | Not applicable |
Overloading versus duplicate definitions
Overloaded Versions in Action
The example below is an original program, written here to illustrate overloading. It defines three subroutines that share the name show. Each one prints a line that tells you which version ran. Each version has a different signature: show(int), show(String), and show(int,int).
int version: 7
String version: hello
two int version: 3 and 4- The name show is shared by three definitions in the same class.
- The three signatures differ in parameter count and parameter types, so the name is overloaded.
- Each call in main selects a version from the actual parameters it supplies.
How Java Picks One Version
When a call uses an overloaded name, the computer does not guess and does not mix the versions up. It looks at the call statement itself and reads the number and types of the actual parameters you supplied. It then selects the one version whose signature matches that pattern. The decision is made from the call, which is why the same name can lead to different subroutines on different lines of a program.
The key word in that process is actual. The computer compares the arguments in the call, not the names you chose inside the subroutine definitions. The decision depends on what you hand over at the moment of the call, and the signatures of the available versions are the only thing it has to match against.
What do you think happens?
In the ShowDemo program, which version of show runs for the call show(3, 4)?
Reveal answer
show(int,int), because the call supplies two int values
The computer reads the actual parameters 3 and 4 in the call. Two values of type int match only the signature show(int,int), so that version runs and prints two int version: 3 and 4.
Mistakes Beginners Make with Overloading
Common mistake Believing that changing only the parameter names creates a new version
What it looks likeWriting show(int n) and show(int count) in the same class and expecting two versions.
Why it failsParameter names are not part of the signature, and the caller does not need them to use the subroutine.
Do this insteadTwo versions need signatures that differ in the number or types of the parameters.
Common mistake Expecting the computer to choose by the variable names inside the definition
Why it failsThe computer compares the actual parameters in the call with the signatures, not the names chosen in the definitions.
Do this insteadLook at what is passed in the call statement, such as the count and types of the values.
Common mistake Thinking a subroutine runs just because it is written in the program
Why it failsA subroutine runs only when the program reaches a call to it.
Do this insteadTrace the calls in main to see which subroutines actually run and in what order.
Where ThreeN2.java Uses Subroutines
The source points to a concrete program that uses subroutines: ThreeN2.java. It is introduced in Section 4.4 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 gives two reasons it 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.
The source does not list the individual subroutines inside ThreeN2.java or show their code, so this article does not describe them in detail. 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. Each named call you find is a place where the work has been divided into a reusable block.
Practice Predicting Overloaded Calls
Using the ShowDemo program, predict which version of show runs for each of these calls, and state the signature you matched: show("world") and show(10). Then say what would change if you added a new definition show(String, int) to the same class.
Hints
- Look only at the actual parameters in the call: how many are there, and what type is each one?
- A String argument matches the signature
show(String), and a single int argument matchesshow(int). - A new
show(String,int)has a different signature from the others, so it can share the name without being a duplicate.
Key Takeaways
- A subroutine is a block of code that can be called multiple times from different parts of a program, and it runs only when the program reaches a call to it.
- A signature is the subroutine's name together with the number and types of its formal parameters, written as
subroutine_name(type1,type2,...); parameter names are left out. - Java allows two subroutines in the same class to share a name when their signatures differ, and the name is then overloaded.
- When a call uses an overloaded name, the computer reads the number and types of the actual parameters in the call and selects the one version whose signature matches.
ThreeN2.javauses subroutines to organize its code and to help format its output in neat columns.