Shadowing in Java
What is shadowing?
A shadowed variable is any identifier that is declared in an inner scope-such as a method parameter, a local variable, or a field of an inner class-with exactly the same name as a variable that already exists in an outer scope. While the inner declaration is in effect, the outer variable can no longer be accessed by its simple name.
Shadowing isn’t inherently wrong! When used intentionally it can make the code more concise and readable. However when it happens by accident it can hide bugs, make the logic harder to follow and increase maintenance cost.
In this article I will show you what are the most common places where shadowing happens and explain how to use it.
Parameter shadowing - Constructor & Setter
public class Person { private String name; // instance field private int age; // instance field
// Parameters have exactly the same names as the fields public Person(String name, int age) { this.name = name; this.age = age; }
public void setAge(int age) { if (age < 0) { throw new IllegalArgumentException("balance can't be negative"); } this.age = age; // explicit field assignment }}Here, both the constructor of Personand the setter of age deliberately shadow their corresponding instance field.
By qualifying the field with this. we refer to the instance variable, even though a parameter with
the same name is scoped.
This makes the code more readable and eliminates the need for weird parameter names like nameParam or ageValue;
Method-local variable shadowing a field
public class Counter { private int count = 0; // instance field
public void increment(int delta) { int count = this.count + delta;
System.out.println("\ttemporary total = " + count);
this.count = count; }
public int getCount(){ return count; }
public static void main(String[] args) { Counter c = new Counter();
System.out.println("initial count = " + c.getCount());
c.increment(5); System.out.println("After first increment = " + c.getCount());
c.increment(3); System.out.println("After second increment = " + c.getCount()); }}Output:
initial count = 0 temporary total = 5after first increment = 5 temporary total = 8after second increment = 8Here shadowing occurs when the line int count = this.count + delta; declares a new local variable count.
From that point until the method ends, the simple name count refers to the local variable, not the instance field
Accessing the hidden field we use this.count to read the original field value before we create the local variable.
Without the this. qualifier the right hand side would have referred to the still uninitialized local variable, producing a compilation error.
After we copy the temporary result back into the instance field with this.count = count;. If we omitted that assignment, the instance field would stay unchanged,
showing that shadowing doesn’t automatically affect the hidden variable.
Inner-class variable shadowing an outer-class variable
public class Outer { private String label = "outer";
public void demonstrate() { String label = "method-local"; System.out.println("Inside method: " + label);
// Inner class class Inner { void show() { String label = "inner-class-local"; System.out.println("Inner sees outer field : " + Outer.this.label);
System.out.println("Inner Class field : " + label); } }
new Inner().show(); }
public static void main(String[] args) { new Outer().demonstrate(); }}Output:
Inside method: method-localInner sees outer field : outerInner Class field : inner-class-localThe innermost declaration always wins when you refer to the identifier by its simple name. To reach a hidden variable that is not the innermost one, you must qualify it:
this.label: the outer instance field of the current class.Outer.this.label: the outer instance field from within an inner class.
Super-class field shadowed by a sub-class field
public class Vehicle { protected double basePrice = 20_000.0;
public double getBasePrice() { return basePrice; }}
public class ElectricVehicle extends Vehicle { protected double basePrice = 35_000.0;
public double getEVCoveredPrice() { return basePrice; }
public double getParentBasePrice() { return super.basePrice; }}
public class Demo { public static void main(String[] args) { ElectricVehicle ev = new ElectricVehicle();
System.out.println("EV price (shadowed) : " + ev.getEVCoveredPrice()); System.out.println("Vehicle price (super) : " + ev.getParentBasePrice()); System.out.println("Via Vehicle ref : " + ((Vehicle) ev).getBasePrice()); }}Output:
EV price (shadowed) : 35000.0Vehicle price (super) : 20000.0Via Vehicle ref : 20000.0Explanation of what is happening:
Vehicledefines a protected fieldbasePrice = 20_000.0.ElectricVehicledeclares another field with the same name (basePrice = 35_000.0).- This shadows the superclass field - any unqualified use of
basePriceinside “ElectricVehicle```refers to the sub-class version.
- This shadows the superclass field - any unqualified use of
- To reach the hidden superclass value you must qualify it with
super.basePrice. - When the object is up-cast to
VehicleandgetBasePrice()is called, the method defined inVehicleruns and returnsVehicle.basePrice.
TL;DR
Shadowing can be a handy tool for readability and for localizing temporary state (method-local or inner-class variables).
Use it deliberately, always qualify the hidden member (this., super. or Outer.this), and enable IDE warnings so
you can’t forget it.
When you follow those simple rules, shadowing becomes an advantage rather than a source of bugs.
Happy coding!
← Back to blog