1

I get the error...

The return type is incompatible with 'Set<Map.Entry<K,T>>' returned from Map<K,T>.entrySet() (mismatching null constraints)

...when implementing a Map and overriding Map.entrySet like this:

package org.abego.util;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
...    
public abstract class MyMap<K, T> implements Map<K, T> {
    private Map<K, T> map = new LinkedHashMap<>();

    @Override
    public Set<java.util.Map.Entry<K, T>> entrySet() {
        return map.entrySet();
    }
    ...
}

The package org.abego.util defines the default nullness to be @NonNull:

@org.eclipse.jdt.annotation.NonNullByDefault
package org.abego.util;

The only way I found to get rid of the error was to 'remove the default nullness annotation' for entrySet using an @NonNullByDefault({}) annotation:

package org.abego.util;
...
import org.eclipse.jdt.annotation.NonNullByDefault;

public abstract class MyMap<K, T> implements Map<K, T> {
    ...
    @Override
    @NonNullByDefault({})
    public Set<java.util.Map.Entry<K, T>> entrySet() {
        return map.entrySet();
    }
    ...
}

While this does work I am wondering if this is the correct way to fix the error.

(I am using Eclipse 4.5 (Mars) and jdk1.8.0_60.)

Nathan
  • 8,093
  • 8
  • 50
  • 76
Udo Borkowski
  • 311
  • 1
  • 9

1 Answers1

0

You are trying to override this method:

Set<Map.Entry<K, V>> entrySet();

with a method whose effective signature is

@NonNull Set<@NonNull Map.Entry<K, V>> entrySet();

This is an incompatible override as can be demonstrated via this caller:

Set<Map.Entry<Foo,Bar>> entries = someMap.entrySet();
entries.add(null);

If someMap has type MyMap then the element type of entries is @NonNull, but the contract of java.util.Map.entrySet() promises a Set where null elements are tolerated (set has not specified nullness of its elements). Mixing both contracts (with @NonNull and unspecified) on the same object entries will break clients that assume non-null elements.

Looking at the Javadoc of entrySet(), it should however be safe to assume that Map.entrySet() will always return a Set with non-null elements. Hence, the root of the problem is in the fact, that java.util.Map.entrySet() has no null annotations.

Since Eclipse Mars, this can be overcome by way of external null annotations. You can tell the compiler that the return type of Map.entrySet() is in fact @NonNull Set<@NonNull Map.Entry<K,V>>, either by applying the Annotate command in the IDE, or by putting the following snippet into a file java/util/Map.eea inside a directory that has been configured as the location for external annotations for the JRE:

class java/util/Map

entrySet
 ()Ljava/util/Set<Ljava/util/Map$Entry<TK;TV;>;>;
 ()L1java/util/Set<L1java/util/Map$Entry<TK;TV;>;>;

With such external annotations in place your program will be accepted by JDT's null analysis.

Nathan
  • 8,093
  • 8
  • 50
  • 76
Stephan Herrmann
  • 7,963
  • 2
  • 27
  • 38
  • Thanks, that solved the issue. I am now using the external null annotation feature for other issues I ran into. Mainly this means adding annotations to JRE methods to "my" external annotations files. As I am probably not the only one running into issues like this I am wondering if there is already some place (like a Git repo or so) that holds the external annotation files for e.g. the Java 1.8 JRE. – Udo Borkowski Sep 14 '15 at 09:28
  • As per https://wiki.eclipse.org/JDT_Core/Null_Analysis/External_Annotations#Collecting_annotations what you request is on our roadmap indeed. First thing on the agenda: knowing that others have produced annotation stub-jars for JRE, Eclipse shall offer a wizard for importing those annotations from jar to our textual format. More to come later... – Stephan Herrmann Sep 15 '15 at 14:23