Java’s visibility rules are tricky at times. Do you know what this will print?
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();
}
}
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
:
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 sub type 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 super type. The author of the change might not even notice, because they’re not looking at your subtype.
Conclusion
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.
Like this:
Like Loading...
It sound strange that the compiler doesn’t complain about trying to override member “x” of class B from subclass C.
C is not a subclass of B, but it is an enclosing type of D, which is an inner class.
You are right, I did a too fast analysis. In this case I don’t have nothing to say: it seems that Java works as I would expect. I don’t see anything strange. The problem you describe can occur in any language.
Yeah, it’s just an ordinary scoping problem…
It seems like you did a number of things very unlike conventional practice to get this to happen. I didn’t even know you could put multiple top-level classes into the same .java file. I certainly wouldn’t have named both static and non-static and outer and inner class members with the same name.
Actually I realize that the in-the-same-file thing isn’t really relevant (whether it’s actually legal or poetic license for the sake of brevity). But I’ve been programming in Java for over 15 years and I don’t think I’ve ever encountered a bug due to this sort of thing.
AHA! The concept you’re looking for is compilation unit (.java file) vs. top level type (classes inside a .java file), out of which only one may be public, and if it is, it must match the .java file name:
https://docs.oracle.com/javase/specs/jls/se8/html/jls-7.html#jls-TypeDeclaration
Don’t say you wouldn’t have those members. Sure, “x” seems like a far-fetched name. But what about something like “name”? Could happen. By accident if you overuse inheritance…
Interesting. (I would have used the term “module” myself, but I said “.java file” to avoid chance of miscommunication.) Is there a particular use that this multiple-top-level-type-per-compilation-unit comes in handy for?
Well, I don’t overuse inheritance. ;^) I also don’t ever import static members that aren’t really constants (i.e. static final String X = “A.x”;).
I’m not sure about the utility of the multiple-top-level-type-per-compilation-unit feature. I mean, all those types are required to be package-private, sure, but then we could still put them in a separate .java file of the package. It would make sense if the types were “somewhat private” as in visible only to the compilation unit, but that sort of visibility doesn’t exist in the JVM, and neither does the whole notion of putting classes in a single compilation unit, because you cannot have several classes per “.class file”.
I guess it’s simply a historic feature where you want to create a quick-and-dirty program, outsourcing some code to separate classes, without the hassle of creating all those files. That’s neat from a readability perspective if you think about it. Other languages built on top of the JVM allow it as well.
Well it’s good to know about for such quick and dirty cases, anyway.
For that “somewhat private” use case you talk about, I often create private static inner classes. I’ve often wished java had just a little scope-control functionality. Something like the c++ friend declaration, or the ability to make a whole package only visible from within the classes of another specific package.
(That should have been “just a little more scope-control functionality”.)
This is certainly reminiscent of (and yet another way to) “shoot your self in the foot in programming language X”.
Lukas has kindly included some of my previous commentary in his (linked article) on “make your classes final”… but I nonetheless protest (mildly) that solving THIS article’s problem by making classes ‘final’ is akin to avoiding stubbing one’s toe by amputating one’s foot. :-)
Sorry, meant to include the link: http://www.toodarkpark.org/computers/humor/shoot-self-in-foot.html
It’s certainly not the main reason for final classes, but it may well be the last straw that breaks the camel’s foot.
Chuckle :-) “Never mix your metaphors before they’re hatched.”
Well played :)
That’s why you should use IDE refactoring tools instead of changing name / visibility manually.
IDEs not fast enough