JDK-8017219 : javac confused by inner classes of the name
  • Type: Bug
  • Component: tools
  • Sub-Component: javac
  • Affected Version: 8
  • Priority: P3
  • Status: Resolved
  • Resolution: Not an Issue
  • Submitted: 2013-06-20
  • Updated: 2013-07-12
  • Resolved: 2013-07-12
Doug Lea reports a possible issue with javaac when trying some experimental changes on java.util.LinkedHashMap. No code example to demonstrate it yet.

I tried doing a fresh jdk8 build with them swapped in,
and saw a compiler error that seems to demonstrate
its confusion about the various nested classes that were
previously all confusingly called "Entry".
Somehow changing the LinkedHashMap class
name from Entry LinkedNode caused it to falsely claim an
ambiguity in a client usage. If this isn't a known javac bug,
it would be good to report it, but I can't. As a workaround, I
modified the client code:

diff -r 1f855dd74077 src/share/classes/java/io/ExpiringCache.java
--- a/src/share/classes/java/io/ExpiringCache.java    Fri Jun 14 07:26:49 2013 -0700
+++ b/src/share/classes/java/io/ExpiringCache.java    Thu Jun 20 06:17:23 2013 -0400
@@ -64,8 +64,8 @@
     ExpiringCache(long millisUntilExpiration) {
         this.millisUntilExpiration = millisUntilExpiration;
-        map = new LinkedHashMap<String,Entry>() {
-            protected boolean removeEldestEntry(Map.Entry<String,Entry> eldest) {
+        map = new LinkedHashMap<String,ExpiringCache.Entry>() {
+            protected boolean removeEldestEntry(Map.Entry<String,ExpiringCache.Entry> eldest) {
               return size() > MAX_ENTRIES;

After further review, I agree with Dan. Here is a corrected version of my second bullet point: - When LinkedHashMap declared a member type called Node (not Entry), then LinkedHashMap inherits a member type called Entry from direct superclass HashMap and another member type called Entry from direct superinterface Map. LinkedHashMap$1 then inherits both member types called Entry from LinkedHashMap. The scope of each of these inherited member types is the entire body of LinkedHashMap$1. The scope of the declaration of Test.Entry also includes the entire body of LinkedHashMap$1, but that is a red herring because each of the inherited member types shadows the declaration of Test.Entry in the body of LinkedHashMap$1. The ambiguity for the simple name Entry in the body of LinkedHashMap$1 is due to the two inherited member types.

There is no javac bug here. Maurizio has identified all the pertinent elements: - an anonymous class (LinkedHashMap$1) - a private member in the anonymous class's direct superclass (LinkedHashMap.Entry) - a non-private member in the anonymous class's indirect superclass (HashMap.Entry) To be precise, the key mechanism is inheritance, not shadowing. Tightening what Maurizio said: - When LinkedHashMap declares a member type called Entry, it hides the member type Entry which would otherwise have been inherited from HashMap. (See JLS 8.5. This hiding occurs whether LinkedHashMap.Entry is private or non-private.) Then, since LinkedHashMap.Entry is private, it's not inherited by LinkedHashMap$1; it is the declaration of Test.Entry that puts the simple name Entry in scope throughout LinkedHashMap$1. - When LinkedHashMap declares a member called Node rather than Entry, then HashMap.Entry is inherited by LinkedHashMap and hence by LinkedHashMap$1, and that puts the simple name Entry in scope throughout LinkedHashMap$1. Since Test.Entry does the same thing, we have two declarations in scope for the same simple name. N.B. When the error message is rendered in a proportional font, the caret for the ambiguous reference to Entry is unfortunately shown under the qualified name "Map.Entry" rather than under the ambiguous simple name Entry in the type argument position.

Agree with Alex's eval, except for one thing: the ambiguity is between HashMap.Entry and Map.Entry, _not_ Test.Entry. Test.Entry is shadowed by these other two inherited members, so it doesn't come into play at all. (If the intent is to refer to Test.Entry inside the signature of 'test', then even the case the compiles is incorrect, because it actually refers to LinkedHashMap.Entry.)

Not sure whether it's a bug; let's start from the version the works, namely, when the name of the nested class in LinkedHashMap is Entry. The LinkedHashMap.Entry class is private - so it shadows all classes/interfaces with same name from supertypes, but it's no inherited in subtypes. That's why, I think, using Entry in the anon subclass of LinkedHashMap is unambiguous: there's only one Entry available there, namely the one in Test. If you change the name of LinkedHashMap.Entry (i.e. to Node, as in the example test case), however, something different happens, as HashMap.Entry is not shadowed anymore, meaning its name will be available in the subclass. Hence, we have two names available, the one inherited from the superclass and the one from the enclosing scope and this causes the compiler error. Note: the same program fails with jdk 5/6/7 AND with Eclipse compiler. Dan/Alex can you confirm it's not a bug?

Test case: interface Map<K, V> { public interface Entry<K, V> { } } class AbstractMap<K,V> implements Map<K, V> { public static class SimpleEntry<K,V> implements Entry<K,V> { } } class HashMap<K, V> extends AbstractMap<K, V> implements Map<K, V> { static class Entry<K, V> implements Map.Entry<K, V> { } } class LinkedHashMap<K, V> extends HashMap<K, V> implements Map<K, V> { private static class Node<K, V> extends HashMap.Entry<K, V> { } protected void test(Map.Entry<K, V> e) { } } class Test { static class Entry { } void test() { new LinkedHashMap<String, Entry>() { protected void test(Map.Entry<String, Entry> e) { } }; } } Output: /home/maurizio/Desktop/Test.java:348: error: reference to Entry is ambiguous protected void test(Map.Entry<String, Entry> e) { } ^ If LinkedHashMap.Node is renamed to Entry, the problem does NOT occur.