A Curious Feature of the Java and Insidious Mistakes that It Can Entail

JAVA Mistakes Main Logo

A Curious Feature of the Java and Insidious Mistakes that It Can Entail

Visibility rules in Java can sometimes be slightly confusing and incomprehensible.

What do you think will be displayed after running this code?

 

package p;
 
import static p.A.x;
 
class A {
    static String x = "A.x";
}
 
class B {
    String x = "B.x";
}
 
class C {
    String x = "C.x";
 
    class D extends B {
        void m() {
            System.out.println(x);
        }
    }
}
 
public class X {
    public static void main(String[] args) {
        new C().new D().m();
    }
}

Will be displayed this:

 B.x 

Because:

 Members of superclass B hide all nested elements of class C, which in turn override static imports of class A. 

How can this all lead to errors?

The problem is not that the above code example is cleverly written by itself. No. Simply, when you write according to this logic, everything will work exactly as expected. But, what happens if we change anything? Well, for example, if you change the access modifier of a superclass member to private:

package p;
 
import static p.A.x;
 
class A {
    static String x = "A.x";
}
 
class B {
    private String x = "B.x"; // Here, the access modifier has been changed
}
 
class C {
    String x = "C.x";
 
    class D extends B {
        void m() {
            System.out.println(x);
        }
    }
}
 
public class X {
    public static void main(String[] args) {
        new C().new D().m();
    }
}

Now, oddly enough, B.x is no longer visible for the m () method, in this case, another rule is applied. Namely:

 Nested elements (classes) hide static import 

And in the end we get the following result:

 C.x 

Of course, we can make some changes again and get the following code:

package p;
 
import static p.A.x;
 
class A {
    static String x = "A.x";
}
 
class B {
    private String x = "B.x";
}
 
class C {
    String xOld = "C.x"; // Changes have been made here as well
 
    class D extends B {
        void m() {
            System.out.println(x);
        }
    }
}
 
public class X {
    public static void main(String[] args) {
        new C().new D().m();
    }
}

As we all know 50% of the variables that will no longer be used are renamed and get the prefix “old”.

In this final version of the code, there remains the only possible value of x in the m () method call and it is statically imported by A.x. Thus, the output will be as follows:

 A.x 

Subtleties that should be considered when working with large software codes

This refactoring showed us that limiting the visibility of an existing element is quite dangerous, since the work of the subclass could depend on it, but by chance, the code turns out to be another, “less important” and more visible member that has the same name that is included in the work and does not allow you to get a compilation error.

  • The same happens when you expand the scope of a member of a class that previously had a private access modifier. Because of this, it may appear in the scope of all its subclasses, although you might not want it.

With static import, you can also face a similar problem. When your code depends on static import, which suddenly turns out to be hidden by another member with the same name, for example, a member of the superclass.

Conclusion

In conclusion, We would like to note once again that you should not too often create subtypes. If you can declare the classes that are created as final, then no one can inherit from them and, in the end, get a sudden “surprise” when you add new members to the superclass. In addition, each time making a change in the scope of existing members of the class, be extremely careful and remember the potential for members with the same names as the variable to change.