Fonctionnalité du langage Java et comment elle a produit un bogue subtil

Java’s visibility rules are tricky at times. Do you know what this will print?

python


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();
    }
}

It will print (highlight to see the solution):

B.x Because:

The super type B's members hide the enclosing type C's members, which again hide the static import from A. How can this lead to bugs? The problem isn’t that the above code is tricky per se. When you write this logic, everything will work as expected. But what happens if you change things? For instance, if you mark the super type’s attribute as private:

python


puts 'package p;
 
import static p.A.x;
 
class A {
    static String x = "A.x";
}
 
class B {
    private String x = "B.x"; // Change here
}
 
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, suddenly, B.x is no longer visible from within method m(), so the rules are now:

Enclosing member hides static import And we get the result of

C.x Of course, we can change this again to 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"; // Change here
 
    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 variables that are no longer needed are renamed to “old”.

Now, in this final version, there’s only one possible meaning of x inside of m(), and that’s the statically imported A.x, thus the output is:

A.x Subtleties across a larger code base These refactorings have shown that making something less visible is dangerous because a subtype might have depended on it, but for some freak coincidence, there was another member in a less “important” scope by the same name that now jumps in and keeps you from getting a compiler error.

The same can happen if you make something that was private more visible. Suddenly, it might be in scope on all its subtypes, even if you didn’t want it to be in scope.

Likewise, with the static import, we could run into a similar issue. When your code depends on a static import, it might suddenly be hidden by some member of the same name, e.g. in a supertype. The author of the change might not even notice, because they’re not looking at your subtype.

Final thoughts:

The conclusion is, once more, not to rely on subtyping too much. If you can make your classes final, no one will ever override them and accidentally “profit” from your newly added members.

Besides that, every time you do a visibility change of your members, be very careful about potential members that are named the same accidentally.

Related blog:

Spring boot interiview questions

Recommended Posts

Fonctionnalité du langage Java et comment elle a produit un bogue subtil
Comment compresser un fichier JAVA CSV et le gérer dans un tableau d'octets
Ecrire une classe en Kotlin et l'appeler en Java
Comment convertir A en A et A en A en utilisant le produit logique et la somme en Java
Comment développer et enregistrer une application Sota en Java
Quelle est la lenteur du scanner Java?
Comment créer un tableau Java
Comment tester une méthode privée et la simuler partiellement en Java
Comment enregistrer JFR (Java Flight Recorder) et générer un fichier de vidage
J'ai écrit une fonction Lambda en Java et l'ai déployée avec SAM
Comment créer une application avec un mécanisme de plug-in [C # et Java]
Qu'est-ce qu'une classe en langage Java (3 /?)
Comment créer un résumé de calendrier Java
Un regard sur Jenkins, OpenJDK 8 et Java 11
[Introduction à Java] Comment écrire un programme Java
[Java] Comment résoudre un bogue dans le compilateur JDK
[Java] Comment sortir et écrire des fichiers!
Qu'est-ce qu'une classe en langage Java (1 /?)
Comment créer un robot Discord (Java)
Qu'est-ce qu'une classe en langage Java (2 /?)
Un ingénieur Java a comparé Swift, Kotlin et Java.
Bases du développement Java ~ Comment écrire un programme (flux et branchement conditionnel) ~
Comment convertir une valeur d'un type différent et l'affecter à une autre variable