Concept of Shadowing in Java

0 votes

I've defined two fields in my class, one of a reference type and one of a primitive type. In the class' constructor, I try to initialize them to some custom values.

When I later query for those fields' values, they come back with Java's default values for them, nullfor the reference type and 0 for the primitive type. Why is this happening?

Here's a reproducible example:

public class Sample {
    public static void main(String[] args) throws Exception {
        StringArray array = new StringArray();
        System.out.println(array.getCapacity()); // prints 0
        System.out.println(array.getElements()); // prints null
    }
}

class StringArray {
    private String[] elements;
    private int capacity;
    public StringArray() {
        int capacity = 10;
        String[] elements;
        elements = new String[capacity];
    }
    public int getCapacity() {
        return capacity;
    }
    public String[] getElements() {
        return elements;
    }
}

I expected getCapacity() to return the value 10 and getElements() to return a properly initialized array instance.

Feb 8 in Java by Sushmita
• 6,840 points
46 views

1 answer to this question.

0 votes

Entities (packages, types, methods, variables, etc.) defined in a Java program have names. These are used to refer to those entities in other parts of a program.

The Java language defines a scope for each name

The scope of a declaration is the region of the program within which the entity declared by the declaration can be referred to using a simple name, provided it is visible (§6.4.1).

In other words, scope is a compile time concept that determines where a name can be used to refer to some program entity.

The program you've posted has multiple declarations. Let's start with

private String[] elements;
private int capacity;

These are field declarations, also called instance variables, ie. a type of member declared in a class body. The Java Language Specification states

The scope of a declaration of a member m declared in or inherited by a class type C (§8.1.6) is the entire body of C, including any nested type declarations.

This means you can use the names elements and capacity within the body of StringArray to refer to those fields.

The two first statements in your constructor body

public StringArray() {
    int capacity = 10;
    String[] elements;
    elements = new String[capacity];
}

are actually local variable declaration statements

local variable declaration statement declares one or more local variable names.

Those two statements introduce two new names in your program. It just so happens that those names are the same as your fields'. In your example, the local variable declaration for capacity also contains an initializer which initializes that local variable, not the field of the same name. Your field named capacity is initialized to the default value for its type, ie. the value 0.

The case for elements is a little different. The local variable declaration statement introduces a new name, but what about the assignment expression?

elements = new String[capacity];

What entity is elements referring to?

The rules of scope state

The scope of a local variable declaration in a block (§14.4) is the rest of the block in which the declaration appears, starting with its own initializer and including any further declarators to the right in the local variable declaration statement.

The block, in this case, is the constructor body. But the constructor body is part of the body of StringArray, which means field names are also in scope. So how does Java determine what you're referring to?

Java introduces the concept of Shadowing to disambiguate.

Some declarations may be shadowed in part of their scope by another declaration of the same name, in which case a simple name cannot be used to refer to the declared entity.

(a simple name being a single identifier, eg. elements.)

The documentation also states

A declaration d of a local variable or exception parameter named n shadows, throughout the scope of d, (a) the declarations of any other fields named n that are in scope at the point where d occurs, and (b) the declarations of any other variables named n that are in scope at the point where d occurs but are not declared in the innermost class in which d is declared.

This means that the local variable named elements takes priority over the field named elements. The expression

elements = new String[capacity];

is therefore initializing the local variable, not the field. The field is initialized to the default value for its type, ie. the value null.

Inside your methods getCapacity and getElements, the names you use in the in their respective return statements refer to the fields since their declarations are the only ones in scope at that particular point in the program. Since the fields were initialized to 0 and null, those are the values returned.

The solution is to get rid of the local variable declarations altogether and therefore have the names refer to the instance variables, as you originally wanted. For example

public StringArray() {
    capacity = 10;
    elements = new String[capacity];
}

Shadowing with constructor parameters

Similar to the situation described above, you may have formal (constructor or method) parametersshadowing fields with the same name. For example

public StringArray(int capacity) {
    capacity = 10; 
}

Shadowing rules state

A declaration d of a field or formal parameter named n shadows, throughout the scope of d, the declarations of any other variables named n that are in scope at the point where d occurs.

In the example above, the declaration of the constructor parameter capacity shadows the declaration of the instance variable also named capacity. It's therefore impossible to refer to the instance variable with its simple name. In such cases, we need to refer to it with its qualified name.

A qualified name consists of a name, a "." token, and an identifier.

In this case, we can use the primary expression this as part of a field access expression to refer to the instance variable. For example

public StringArray(int capacity) {
    this.capacity = 10; // to initialize the field with the value 10
    // or
    this.capacity = capacity; // to initialize the field with the value of the constructor argument
}

There are Shadowing rules for every kind of variable, method, and type.

My recommendation is that you use unique names wherever possible so as to avoid the behavior altogether.

answered Feb 8 by developer_1
• 3,280 points

Related Questions In Java

0 votes
1 answer

What is the concept of Immutability for strings in Java ? Why are strings immutable ?

According to Effective Java, chapter 4, page 73, ...READ MORE

answered May 11, 2018 in Java by Rishabh
• 3,540 points
125 views
0 votes
2 answers

One line initialization of an ArrayList object in Java

In Java 8 or earlier: List<String> string = ...READ MORE

answered Jul 26, 2018 in Java by samarth295
• 2,190 points
109 views
0 votes
2 answers

Counting no of Occurrence of a particular character inside a string in Java

We can find out the no. of ...READ MORE

answered Sep 7, 2018 in Java by Sushmita
• 6,840 points
184 views
0 votes
2 answers

How can I separate the digits of an int number in Java?

You can convert a number into String ...READ MORE

answered Aug 10, 2018 in Java by Sushmita
• 6,840 points
462 views
0 votes
2 answers

“Could not find or load main class” mean?

Use the final modifier to enforce good initialization. Avoid returning ...READ MORE

answered Sep 18, 2018 in Java by Sushmita
• 6,840 points
1,390 views
0 votes
2 answers

What is the use of toString method in Java and how can I use it ?

Whenever you require to explore the constructor ...READ MORE

answered Aug 23, 2018 in Java by Daisy
• 8,040 points
305 views
0 votes
1 answer

How do I replace character from string at specific indexes?

String are immutable in Java. You can't ...READ MORE

answered Apr 26, 2018 in Java by Rishabh
• 3,540 points
31 views
0 votes
2 answers

When to use Static Methods in Java?

A static method has two main purposes: For ...READ MORE

answered Aug 9, 2018 in Java by samarth295
• 2,190 points
78 views
0 votes
3 answers

Check if a String is numeric in Java

Java 8 Lambda Expression is used: String someString ...READ MORE

answered Sep 3, 2018 in Java by Daisy
• 8,040 points
557 views
0 votes
3 answers

How can I add new elements to an Array in Java

String[] source = new String[] { "a", ...READ MORE

answered Sep 19, 2018 in Java by Sushmita
• 6,840 points
84 views