Can someone explain this mysterious riddle of Java?

A friend at work extended LinkedHashMap and redefined removeEldestEntry , similar to:

 import java.util.LinkedHashMap; import java.util.Map.Entry; public class CompileTest { static class MyMap<K, V> extends LinkedHashMap<K, V> { protected boolean removeEldestEntry(Entry<K, V> eldest) { return true; } } } 

Note that the parameter class is Entry , not Map.Entry .
Eclipse generated a method signature. IntelliJ shows an error, complains that Entry has private access in java.util.LinkedHashMap and prefers Map.Entry . But it still compiles anyway.

I wrote a small example for experiments:

 public class CompileTest { static class A{ public class Inner { } public void doStuff(Inner a){} } static class B extends A{ private class Inner { } } static class C extends B { public void doStuff(Inner a) { } } } 

Now IntelliJ does not show an error, but the class does not compile. Here are two situations that seem to be the same when both the IDE and the compiler seem to have alternating behavior, and also never agree with each other.

Can someone explain this?

+4
source share
3 answers

From Java Language Specification - 8.5 Element Type Declaration

If a class declares a member type with a specific name, then a declaration of this type is called to hide all available declarations of member types with the same name in superclasses and superinterfaces of the class.

A class inherits from its direct superclass and direct superinterfaces, all types of infrequent members of the superclass and superinterfaces that are available for code in the class, rather than a hidden declaration in the class.

It can be concluded that:

B.Inner hiding A.Inner . B does not inherit A.Inner . A.Inner not a member of (8.2) type B C cannot inherit A.Inner from B C cannot inherit B.Inner from B because it is closed.

Therefore, C not an Inner member type. Assume that there is no other type of Inner in areas of the C environment (external class, compilation unit, package), then the type name Inner cannot be resolved.

javac trying to report a more detailed error, but this is only a guess at your intention. Even the best error message should probably include all of the above explanations.

In the first example of Entry the import statement declares Entry in the entire scope of the compilation unit (that is, in the java file), so Entry allowed to be Map.Entry

IntelliJ 10.5 does not complain about the first example; apparently bug fixed. In the second example, this is still wrong.

There is something funny in private . Why does the specification explicitly exclude private members, while it already requires members to be "accessible"? In fact, B.Inner is available throughout CompileTest (6.6.1), including C C may have doStuff(B.Inner) and it will be compiled.

Probably why IntelliJ screwed up the second example; he believes B.Inner is available for C , so C inherits B.Inner . He did not take into account the proposal for the additional exclusion of private members in inheritance.

This shows two conflicting views on the scope of the private declaration. View # 1 requires the scope to be the immediate spanning class, view # 2 is the class spanning the top level. # 2 less well-known to programmers, they are surprised to learn that C can access B.Inner . # 2 can be justified as follows: all the same, they are all in one file, therefore there is no need for encapsulation, we do not protect anyone by denying access; allowing access in most cases is more convenient.

View # 2 probably also argued that C should inherit B.Inner - what could go wrong? Interestingly, No. 2 wins the general access control rule, but loses the inheritance rule.

The spectrum is really really dirty, fortunately, we usually do not encounter these difficult situations. This is more likely a bug LinkedHashMap and HashMap , which renames the public name for private use.

+4
source

This is because Entry and Map.Entry are two different classes. The former has a default scope and is defined in HashMap , the latter is a public interface . Thus, you are using a private parameter in a method with a wider scope. This compiles, but it's probably not your intention.

+1
source

You just need to indicate which "Internal" you are referring to. An IDE is just a tool to help you; the compiler is always the final link.

 ... static class C extends B { public void doStuff(A.Inner a) { } } ... 

For further clarification, you can get the same "subtle / weird" behavior using standard variables. A private definition hides a public definition.

 public class CompileTest { static class A { public static Object foo; } static class B extends A { private static Object foo; } static class C extends B { public void doStuff() { foo = new Object(); // Will not compile - 'foo' is not visible A.foo = new Object(); // Works } } } 
0
source

All Articles