Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8 Double curly bracket initialization and name collision

The following class has an inner class called Entry. This code will not compile in Java 8 as the compiler assumes the Entry referenced within the double curly brace initializer is of type Map.Entry and not Scope.Entry. This code compiles in previous versions (at least 6 and 7) of the JDK but is broken in JDK 8. My question is "why?" Map.Entry is not imported in this class, so there is no reason for the compiler to assume that the value is of type Map.Entry. Is there some implicit scope being brought in or something for anonymous classes?

Error:

scope/Scope.java:23: error: incompatible types: scope.Scope.Entry cannot be converted to java.util.Map.Entry for (final Entry entry : entries) {
scope/Scope.java:22: error: cannot find symbol put(entry.getName(), entry);

Example Code:

package scope;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

public class Scope {

    public static class Entry<T> {
        public String getName() {
            return "Scope";
        }
    }

    public static void main(String[] args) {
        final Set<Entry> entries = new HashSet<>();

        new HashMap<String, Entry>() {{
            // Why does the Java 8 compiler assume this is a Map.Entry
            // as it is not imported? 
            for (final Entry entry : entries) {
                put(entry.getName(), entry);
            }
        }};
    }
}
like image 801
Briggs Avatar asked Nov 13 '14 16:11

Briggs


People also ask

What is the purpose of {} in Java?

It's called an initializer block and is invoked every time an instance of the class is created. The Java compiler copies initializer blocks into every constructor. Therefore, this approach can be used to share a block of code between multiple constructors.

What do double brackets mean in Java?

Double brace initialisation creates an anonymous class derived from the specified class (the outer braces), and provides an initialiser block within that class (the inner braces). e.g. new ArrayList<Integer>() {{ add(1); add(2); }};

How do you replace open curly braces in Java?

Summary. If one needs to use the replace() function to replace any open curly brackets "{" contained within a String with another character, you need to first escape with a backslash "\". This syntax not only applies to curly brackets { }, but also with "square brackets" [ ] and parentheses ( ).


1 Answers

It's definitely not a bug, it's a side-effect of using double-brace initialization.

new HashMap<String, Entry>() {{
    for (final Entry entry : entries) {
        put(entry.getName(), entry);
    }
}};

This type of initialization is basically a clever way to abuse instance initialization blocks. It creates an anonymous subclass of HashMap with an initialization block, and then copies that block into the beginning of its default constructor before calling it. This subclass gives priority to the Entry in the scope of its parent, rather than the scope that it's nested in. This is explained by shadowing.

From 8.1.6. Class Body and Member Declarations

If C itself is a nested class, there may be definitions of the same kind (variable, method, or type) and name as m in enclosing scopes. (The scopes may be blocks, classes, or packages.) In all such cases, the member m declared in or inherited by C shadows (§6.4.1) the other definitions of the same kind and name. [emphasis mine]

Here, C is the anonymous inner class declared. Since it inherits from HashMap, java.util.Map.Entry shadows scope.Scope.Entry.

As for why it compiled as you wanted it to with previous versions, I have no idea. This behavior was present in those versions (the docs I referenced are from 7), so it shouldn't have worked. So maybe those versions are bugged.

like image 111
Floegipoky Avatar answered Nov 09 '22 05:11

Floegipoky