Writing Functions and Subroutines
A function is written like a regular subroutine, with the addition that you must specify the value it returns.
Getting Values Back from Calls You Already Make
You have already used functions without writing any yourself. Math.sqrt computes a square root, and TextIO.getInt reads an integer that the user types. In both cases the function gives your program a value, and the program can then store it, print it, or use it in a further calculation.
The natural next question is how to write functions of your own that provide a value in the same way. The answer is the return statement. Once you know its syntax and where it sits inside a function, you can write a function that computes a result and delivers it back to whatever part of the program called it. This article focuses on the syntax first, then on how to read a return statement and predict what it produces.
Anatomy of a Return Statement
A function is written in much the same way as a regular subroutine. Its overall form is the same, with one addition: you must specify the value that the function is to return. The return statement is the place where that value is specified.
A function is written like a regular subroutine, with the addition that you must specify the value it returns. A return statement has the form return followed by an expression, and that expression supplies the value handed back.
The template has two parts. The keyword return begins the statement. The expression that follows it is the value to be returned. An expression is anything that produces a value, such as a single variable name, a number, or a calculation. The source writes the template with a closing semicolon, and that semicolon is part of the notation as the source presents it. The part of the syntax to learn above all is the keyword followed by an expression.
Read the strip from left to right. The keyword is the anchor of the statement, and the expression is the part that gets evaluated. The expression is what determines the result. If you change the expression from total times 2 to total plus 2, the returned value changes, while the keyword stays exactly the same. So when you read any return statement, the first question to ask is what its expression evaluates to at the moment the function runs.
Evaluating the Return Expression
Before reading on, try to work out what the following function gives back. It takes one number, and its return statement names an expression built from that number. The function is shown as a generated illustration of the pattern the source describes, with an input of 4.
- The keyword return begins the statement.
- The expression total * 2 is evaluated using the input total.
- Only the result of the expression leaves the function.
What do you think happens?
The call is doubleIt(4). What does the program print?
Reveal answer
8
The return statement hands back the value of its expression, not the input. The expression total * 2 is evaluated with total equal to 4, giving 8, and only that 8 leaves the function. The 4 is the input, which never comes back out by itself.
8Tracing the value from call to printed output
Trace what happens when the main program executes System.out.println(doubleIt(4)).
- 1Step 1: the call
The main program calls
doubleItand passes in 4, so the input total starts out as 4. - 2Step 2: the expression
The return statement's expression total * 2 is evaluated with total equal to 4, producing 8.
- 3Step 3: the hand-back
Only the value 8 leaves the function. The call is finished once the expression has been handed back.
- 4Step 4: the place of the call
The value 8 appears where the call was made, so println prints 8.
The program prints 8, not 4.
This is the central idea of the return statement. The function sends a computed value back to the place where it was called. The return statement is the last thing the function does with its value, so once the expression has been evaluated and handed back, the call is complete.
Subroutines Are Written Once and Called Many Times
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.
A subroutine is a block of code that can be called multiple times from different parts of a program. It runs when the program reaches a point where it calls it, not merely because it exists in the program.
The important word is called. 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. Writing the instructions in one place means that a change to the job is made once rather than in every place the job is needed.
The diagram shows the path a single call takes. Control leaves the main program at the call, runs the subroutine's body, and returns to the place where the call was made. For a function, the return statement is what determines the value that travels back along that return path.
Tracing Repeated Calls in One Run
Because a subroutine runs only when it is called, the order of the run is set by the main program. Suppose a main program calls the same subroutine at three different points. Each time the main program reaches a call, it pauses its own work, the subroutine runs, and then the main program carries on from the next line after that call.
Read the diagram from top to bottom. The main program never runs the subroutine on its own; every run of the subroutine begins with a call from the main program and ends with a return to it. The same block of code appears three times in the sequence, but it is written only once in the program.
Function Versus Regular Subroutine
A function and a regular subroutine share the same overall form. What a function adds is the requirement to specify the value it returns. A regular subroutine does not need a value to be handed back in this way, so its written form ends without a return of a computed value.
| Aspect | Regular subroutine | Function |
|---|---|---|
| Overall form | Block of code that can be called many times | Same overall form as a regular subroutine |
| Calling | Runs when the program reaches a call | Runs when the program reaches a call |
| Value handed back | No value is specified by a return statement | Must specify the value returned, using return followed by an expression |
| What comes back | Not applicable in this comparison | The result of evaluating the expression, not the input |
What stays the same and what a function adds, based on the source's description
Finding the Return Statement Behind a Surprise
When a program prints a number you did not expect, the source recommends a fixed order of checks. Follow the chain in this order: the call, the expression in the return statement, and then the value that appears where the call was made. Checking the return statement and the expression feeding it comes first.
Common mistake Assuming the function returns its input
What it looks likeWith doubleIt(4) the programmer expects 4 to come back.
Why it failsThe return statement hands back the value of its expression, not the input. The expression is evaluated first, and only its result leaves the function.
Do this insteadEvaluate total * 2 with total equal to 4, which gives 8, and expect 8 at the call site.
Common mistake Looking at the call site first and not the return statement
What it looks likeA printed 14 is blamed on println, and the function is never opened.
Why it failsThe value printed is whatever the return statement handed back. The call site only shows where that value appears.
Do this insteadOpen the function, read its return statement, and evaluate the expression before examining anything else.
Common mistake Changing the keyword instead of the expression
Why it failsThe keyword return stays the same in every return statement. The expression is what determines the result.
Do this insteadWhen the result is wrong, change or inspect the expression after return, not the keyword.
Subroutines in ThreeN2.java
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 for the subroutines: code organization and output formatting.
Code organization means that separating the work into named blocks makes the program easier to read and manage. Output formatting means that the program prints its results in neat columns, and the subroutines are part of how that formatting is achieved.
Checking Your Prediction on a Changed Expression
Keep the same doubleIt function and call, but change its return statement so that the expression is total plus 2 instead of total times 2. Predict what the program now prints for the call doubleIt(4), then check your prediction by evaluating the new expression with total equal to 4.
Hints
- The keyword return stays the same. Only the expression after it changes.
- Evaluate the new expression with total equal to 4 before looking at the printed result.
- Only the value of the expression leaves the function, so ask which number that expression produces.
Key Takeaways
- A function is written like a regular subroutine, with the addition that you must specify the value it returns through a return statement.
- A return statement has the form return followed by an expression. The value handed back is the result of evaluating that expression, not the input passed to the function.
- A subroutine is a block of code that runs whenever a program calls it, so writing it once lets the program reuse it at many points in one run.
- When a result is unexpected, check the return statement and the expression feeding it first, then the value that appears where the call was made.
ThreeN2.javauses subroutines for code organization and for printing its output in neat columns.