Subroutines and Formal Parameters
The scope of a name is the part of the source code where that name is valid, and it is determined by where the declaration appears.
One Block of Code, Many Calls
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.
What do you think happens?
If a program needs the same calculation or the same printed output in several places, where would you rather keep the instructions: in one place, or in every place they are needed?
Reveal answer
In one place. A subroutine lets the instructions be written once and then called from each place they are needed.
The point of a subroutine is that it is written once and called many times. Each call reuses the same block of code rather than repeating its lines.
The important word in that description 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 one run of the program. To understand how the compiler decides which name means what across all those moments, we need the idea of scope.
What Scope Means for a Name
When Java executes a variable declaration, memory is allocated for that variable. The name can then be used in some part of the source code to refer to that memory, or to the data stored there. The part of the source code where the name is valid is called its scope. The same idea applies to subroutine names and to formal parameter names.
The scope of a name is the part of the source code where that name is valid. It is determined by where the declaration appears, not by what the program does while it is running.
Scope is a property of the source text. Reading the code tells you the scope of a name; you do not have to run the program to find it out.
Two common assumptions cause confusing errors. The first is that once a name is declared, it can be used anywhere in the program. The second is that a new declaration with an old name simply overwrites the old one. Java does not let a name float freely across a program. Each name has a region of source code where it is meaningful, and understanding that region is the key to reading unfamiliar code correctly.
When a Local Name Hides a Member Variable
Java allows a local variable or formal parameter to share a name with a member variable. Inside the scope of that local variable or parameter, the member variable is hidden: the simple name refers to the local one. The member variable is not destroyed or made inaccessible. It is simply not reachable by its simple name in that region.
0
5
0- count on the static line is a member variable in scope across the whole Game class
- Inside
playGame(), the local declaration int count = 5 hides the member variable for the simple name count - Game.count uses the full name and reaches the hidden member variable even inside
playGame() - In
main(), there is no local count, so the simple name refers to the member variable
Read the example as a map. Inside the body of playGame(), the simple name count points to the local variable. Everywhere else in the Game class, the simple name count points to the member variable, unless some other local variable or parameter named count hides it in that region. The member variable can still be reached through its full name, Game.count, and this full name may be used even inside the class, including inside playGame(). The general rule is that the simple name of a hidden member variable must be replaced by its full form, className.variableName, to reach it.
Why a Redeclared Name Is an Error
Many languages would allow an inner declaration to hide an outer one. Java adds a stricter rule on top of the scope regions: it is not legal to redefine the name of a formal parameter or local variable within its scope, even in a nested block. The compiler rejects the second declaration, which is a compile-time error.
- total is declared at the top of
main()and its scope runs to the end ofmain() - The second declaration of total is inside the nested for block, which is still within the scope of the first
- Java rejects this with a compile-time error, so the program will not run
The same rule that forbids overlapping declarations also explains when a name becomes free again. Once the block in which a variable was declared ends, its scope has ended, and the name is available for a new declaration. Java does not keep the old name reserved after its region closes. This is why two separate for loops can each declare their own i.
0
1
0
1
2- The first i belongs only to the first for statement
- When that statement ends, its name is free, so the second for statement may declare a new i
- Neither loop sees the other's i
Subroutine Names and Variable Names
A natural question follows: can a local variable hide a subroutine name the same way it hides a member variable? The source says this cannot happen. Java has no rule forcing variables and subroutines to have different names, so the same name may legally be used for both in one class. The compiler can always tell which kind of name is meant, because a subroutine name is always followed by a left parenthesis.
This is also why subroutine names are often written with parentheses, as in main(). The source suggests thinking of the parentheses as part of a subroutine's name. Class names follow similar logic: a class name is a type, so it can be used to declare variables and formal parameters, and to specify a subroutine's return type. The source gives a deliberately extreme case, a class called Insanity whose subroutine has Insanity as its name, its return type, and the type of its parameter. Its verdict is that legal does not mean wise, so that example demonstrates the rules rather than a style to imitate.
Tracing Repeated Calls from main
Now trace what happens during a run. The main program is the place where execution starts, and each call to a subroutine transfers control to that subroutine's body. When the subroutine finishes, control returns to main at the point just after the call, and main continues. A single subroutine can therefore be entered at several points in the same run, once for each call.
1
-1
2
-1
3printSeparatoris written once, but it is called twice from main- Each call sends control into the subroutine, and the program resumes in main right after the call
- The output shows the subroutine's line appearing at both call points
Read the diagram top to bottom. The first call runs printSeparator and then control returns to main, which prints 2. The second call repeats the same subroutine body and returns again, so main prints 3. Both calls use one copy of the code, and the position in main determines where execution resumes.
Formal Parameters at the Call Site
A subroutine can also declare formal parameters. A formal parameter's scope is the body of its subroutine, so its name exists only inside that body. The caller has its own names in its own scope. In the example below, the name x lives in main, while the name n lives only inside showSquare. The two names are different declarations in different regions of source code.
16
49- n is a formal parameter whose scope is the body of
showSquare - x is a local variable of main, and it is not visible inside
showSquare - The same subroutine is called twice with different arguments
Mistakes Beginners Make with Scope
Common mistake Assuming a declared name can be used anywhere in the program
What it looks likeUsing a local variable total in one method and expecting it to exist in another method
Why it failsA local variable's scope runs only to the end of the block where it was declared, so the name is not valid elsewhere.
Do this insteadUse the name only inside the region where it was declared, or pass the value through a formal parameter.
Common mistake Assuming a new declaration simply overwrites the old one
What it looks likeDeclaring int total = i inside a nested block while an outer int total is still in scope
Why it failsJava forbids redefining a formal parameter or local variable name within its scope, even in a nested block, so this is a compile-time error.
Do this insteadChoose a different name for the inner variable, or move the declaration outside the nested block if that is what you intend.
Common mistake Expecting the simple name to reach a hidden member variable
What it looks likeWriting count inside playGame() and expecting the static member value 0 when a local count is declared there
Why it failsThe local declaration hides the member variable for the simple name in that region.
Do this insteadWrite the full name Game.count to reach the member variable.
Common mistake Believing a loop counter remains available after its for statement
What it looks likeUsing i after a for loop that declared int i in its header
Why it failsA loop control variable declared in the for header is valid only within that for statement.
Do this insteadDeclare a new variable before the loop if its value must be used afterward.
How ThreeN2.java Uses Subroutines
The source describes ThreeN2.java, introduced in Section 4.4 as an improved version of the 3N+1 program, as a program that uses subroutines and prints its output in neat columns. It is the source's own concrete example of the idea in an actual program.
- Code organization: separating work into named blocks makes the program easier to read and manage
- Output formatting: the program prints its results in neat columns, and its 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. Those two sets of places show how the subroutines organize the program and shape its columns.
Practice: Find the Scope and the Output
Given the class below, write the output and state which declaration the simple name count refers to inside playGame() and inside main(). Explain how to reach the other declaration.
public class Practice {
static int count = 2;
static void playGame() {
count = 7;
int count = 3;
System.out.println(count);
}
public static void main(String[] args) {
playGame();
System.out.println(count);
}
}Hints
- Ask which declaration of count is in scope on each line, starting from the declaration inside
playGame() - Recall that a local declaration hides the member variable for the simple name within its region
- Consider which line is the one that comes before int count = 3 in the same block, and what that means for its scope
Key Takeaways
- The scope of a name is the part of the source code where that name is valid, and it is determined by where the declaration appears.
- A static member variable or static subroutine is in scope across its entire class, and a local variable or formal parameter may hide a member variable, which must then be reached through its full name,
className.variableName. - Java forbids redefining a formal parameter or local variable name within its scope, even in a nested block, but the name becomes reusable once its block ends.
- A subroutine is a block of code that can be called multiple times from different parts of a program, and it runs each time a call reaches it.
ThreeN2.javauses subroutines to organize its code and to format its output in neat columns.