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);
}
}};
}
}
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.
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); }};
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 ( ).
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With