Scope decides where a name is visible in a Java program: a local variable lives from its declaration to the end of the block it was declared in, and shadowing is what happens when a name in a smaller scope hides the same name outside it. Java allows some shadowing and rejects the rest, and the rules are worth knowing before they surprise you.
Where is a local variable visible?
A local variable declared inside a block is visible from the point of its declaration to the end of that block, including any nested blocks inside it. Once the block ends the name is gone, and the compiler will not let you use it again.
public class LocalScope {
public static void main(String[] args) {
int x = 10;
if (x > 0) {
int y = 5;
System.out.println("inside the block: x=" + x + " y=" + y);
}
System.out.println("outside the block: x=" + x);
}
}The output is:
inside the block: x=10 y=5
outside the block: x=10The variable x is visible in both places because it was declared in the method body. The variable y lives only inside the if block. Declaring variables in the smallest block that needs them keeps them out of the way of everything else.
What happens when you use a variable outside its scope?
The compiler catches it before the program runs. If you move the print statement in the example above out of the if block, javac reports a missing symbol:
OutOfScope.java:6: error: cannot find symbol
System.out.println(y);
^
symbol: variable y
location: class OutOfScope
1 errorThis is a compile-time error rather than a runtime surprise, which is one of the real advantages of a statically typed language. The fix is to declare the variable in a block that contains every place you want to use it.
Why do some shadowing attempts fail to compile?
Java allows a local variable to hide a field, but it does not allow one local variable to hide another local variable that is already in scope. Trying to redeclare a name inside a nested block gives you this:
ShadowClash.java:5: error: variable size is already defined in method main(String[])
int size = 7;
^
1 errorThe reason is that a hidden local makes the outer one impossible to reach by name, and the compiler refuses to let a method contain two locals whose names overlap. The same rule applies to a for loop variable that clashes with a local declared just above it. If you genuinely need a second value with a similar name, give it a different name.
When does a local shadow a field?
A field is the variable declared directly in a class body, and it is visible to every method of that class. A local variable with the same name inside a method hides the field for the rest of that method. Java allows this, and the way to reach the field anyway is the this keyword.
public class FieldShadowing {
int count = 5;
void report() {
int count = 99;
System.out.println("local count = " + count);
System.out.println("field count = " + this.count);
}
boolean atLeast(int count) {
return count >= this.count;
}
public static void main(String[] args) {
FieldShadowing tracker = new FieldShadowing();
tracker.report();
System.out.println("7 is at least 5? " + tracker.atLeast(7));
System.out.println("3 is at least 5? " + tracker.atLeast(3));
}
}The output is:
local count = 99
field count = 5
7 is at least 5? true
3 is at least 5? falseInside report, the bare name count means the local one, and this.count means the field. The same happens with a parameter named count in the second method. Shadowing a field is legal, but it is also the easiest way to confuse yourself, because a reader sees the name and has to work out which of the two is meant. Using this. to be explicit, or choosing a different name for the local, avoids the puzzle entirely.
How does scope work inside loops?
A counter declared in the header of a for loop belongs to that loop and disappears when the loop ends. A while loop has no header to hold one, so the variable has to be declared outside the loop and stays visible afterwards.
public class LoopScope {
public static void main(String[] args) {
for (int i = 0; i < 2; i++) {
System.out.println("inside the for loop: i = " + i);
}
int j = 0;
while (j < 2) {
j++;
}
System.out.println("after the while loop: j = " + j);
}
}The output is:
inside the for loop: i = 0
inside the for loop: i = 1
after the while loop: j = 2The value of i is out of reach once the loop finishes, while j is still there with its final value. That difference is the practical reason the for loop is the better default when the counter is only needed by the loop itself.
Can two methods use the same local name?
Yes. Each method has its own scope, so two methods can both declare a variable called count without any conflict.
public class IndependentNames {
static int rows() {
int count = 3;
return count;
}
static int columns() {
int count = 10;
return count;
}
public static void main(String[] args) {
System.out.println(rows() + " rows by " + columns() + " columns");
}
}The output is 3 rows by 10 columns. The two variables never meet, so they can share a name, and doing so keeps each method reading on its own terms.
What do you do when a name clashes?
Rename one of them. There is no clever syntax that lets two locals in overlapping scopes share a name, and trying to work around it usually makes the code worse.
public class RenameToFix {
static double addTax(double total, double rate) {
double totalWithTax = total * (1 + rate);
return totalWithTax;
}
public static void main(String[] args) {
System.out.printf("%.2f%n", addTax(80.00, 0.175));
}
}The output is 94.00. The parameter is total and the local is totalWithTax, so nothing overlaps and both names say what they hold. When a clash is between a local and a field, the same rule applies, and this.fieldName is the other option when you do want the field.
Takeaways
| Where a name is declared | How long it lives | Who can see it |
|---|---|---|
| Method body | Until the method returns | That method, and any nested block inside it |
| Block inside a method | Until the block ends | That block and the blocks nested in it |
| For loop header | Until the loop ends | The loop body only |
| Class body, as a field | As long as the object exists | Every method of the class |
| Method parameter | Until the method returns | That method |
- A local is visible from its declaration to the end of its block, and using it outside is a compile error.
- A local may hide a field, and
this.fieldreaches the field when you need it. - A local may not hide another local that is already in scope; the compiler rejects it.
- Two methods can use the same local name without any conflict at all.
- When two names clash, rename one of them rather than hunting for a workaround.
Try it yourself: write a method with a local called total, and a field called total, then print both using this to make the difference visible. Next, try to declare a local with the same name inside a nested block and read the compiler message. Finally, declare a counter in a for loop header and try to use it after the loop, then move the declaration outside the loop and confirm the program compiles and that the counter still holds its final value.