Concepts / Writing Subroutines with Parameters

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,...).

  • Programming

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

Definition

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.

main methodcallershowLabel subroutineone stored block of code1. call showLabel("Total", 3)2. finish, resume after first call3. call showLabel("Done", 1)4. finish, resume after second call
When main calls showLabel at two points, in what order do control and resumption move between main and the subroutine?

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.

doTasksubroutine nameintfirst parameter typedoublesecond parameter typebooleanthird parameter type
Which tokens of the signature identify the subroutine, and what does each position mean to a caller?
Worked example

Writing the signature of showLabel

A subroutine takes a name as text and a count as a whole number. What is its signature?

  1. 1
    Find the name

    The subroutine is called showLabel.

  2. 2
    Count the parameters

    There are two formal parameters: one for the name and one for the count.

  3. 3
    List the types in order

    The first parameter is text, which is a String, and the second is a whole number, which is an int.

  4. 4
    Assemble the signature

    Write the name, then the types in order, separated by a comma: showLabel(String,int).

Result

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.

FeatureOverloaded subroutinesDuplicate subroutines
NameSameSame
Number and types of parametersDifferIdentical
Allowed in the same classYesNo, the computer cannot tell them apart
Which one runsChosen from the actual parameters in each callNot 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).

java
Output
int version: 7
String version: hello
two int version: 3 and 4
Notes on the code
  • 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.

call show(3, 4)actual parameters: 3 and 4Compare parametercounttwo actual parametersCompare parametertypesint and intshow(int,int) runsthe only matching signatureread the callonly two-parameter versions remaintypes match exactly one signature
Given a call with a specific number and types of actual parameters, how does Java narrow the candidate signatures down to one version to run?

What do you think happens?

In the ShowDemo program, which version of show runs for the call show(3, 4)?

  • show(int), because 3 is an int

  • show(String), because numbers are printed as text

  • show(int,int), because the call supplies two int values

Reveal answer
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 like

    Writing show(int n) and show(int count) in the same class and expecting two versions.

    Why it fails

    Parameter names are not part of the signature, and the caller does not need them to use the subroutine.

    Do this instead

    Two 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 fails

    The computer compares the actual parameters in the call with the signatures, not the names chosen in the definitions.

    Do this instead

    Look 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 fails

    A subroutine runs only when the program reaches a call to it.

    Do this instead

    Trace 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

EASY

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 matches show(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.java uses subroutines to organize its code and to help format its output in neat columns.