Search code examples
javaeclipsenon-nullablejava-annotations

How to get rid of "Null type safety" warning in eclipse neon for TreeMap


How to get rid of the warning in this example code.

I use Eclipse Neon with Java 1.8 and org.eclipse.jdt.annotation_2.1.0

import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;

@NonNullByDefault
public class NullAnnotationTest4 {

    public static void main(String[] args) {

        final TreeMap<@Nullable Integer, @Nullable String> treeMap = new TreeMap<>();

        treeMap.put(3,  "Test1");
        treeMap.put(null, null);

        //This produces the warning
        final Set<@Nullable Entry<@Nullable Integer, @Nullable String>> set = treeMap.entrySet();

        for (final Iterator<@Nullable Entry<@Nullable Integer, @Nullable String>> it = set.iterator(); it.hasNext(); ) {

            final Entry<@Nullable Integer, @Nullable String> entry = it.next();

            if (entry != null && entry.getKey() == null && entry.getValue() != null)
                System.out.println(entry.getKey()+" is mapped to "+entry.getValue());
        }

    }
}

The warning is:

Null type safety (type annotations): 
The expression of type 
'Set<Map.Entry<@Nullable Integer,@Nullable String>>' 
needs unchecked conversion to conform to 
'Set<Map.@Nullable Entry<@Nullable Integer,@Nullable String>>'

I tried several combinations of @Nullable and @NonNullable. Even ? extends as was suggested in a similar case here: https://bugs.eclipse.org/bugs/show_bug.cgi?id=507779

But the warning always only moves but never goes completely away.

Update:

I got rid of the warning by using this line:

final Set<? extends @Nullable Entry<@Nullable Integer, @Nullable String>> set = treeMap.entrySet();

But I am totally lost as of why. Seems to me I trick the validator in loosing track or something and the code really starts to get ugly.

Full new code:

import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;

@NonNullByDefault
public class NullAnnotationTest4 {

    public static void main(String[] args) {

        final TreeMap<@Nullable Integer, @Nullable String> treeMap = new TreeMap<>();

        treeMap.put(3,  "Test1");
        treeMap.put(null, null);


        final Set<? extends @Nullable Entry<@Nullable Integer, @Nullable String>> set = treeMap.entrySet();

        for (final Iterator<? extends @Nullable Entry<@Nullable Integer, @Nullable String>> it = set.iterator(); it.hasNext(); ) {

            final Entry<@Nullable Integer, @Nullable String> entry = it.next();

            if (entry != null && entry.getKey() == null && entry.getValue() != null)
                System.out.println(entry.getKey()+" is mapped to "+entry.getValue());
        }

    }
}

Update 2 Shorty pasted the wrong code.


Solution

  • At the core of the question types like Set<@NonNull X> and Set<@Nullable X> are incompatible: neither is assignable to the other.

    To wit: if you have a Set<@NonNull X> clients expect to extract non-null elements and will break if the set is actually Set<@Nullable X>. Conversely, if you have a Set<@Nullable X> clients expect to be able to insert null into the set and will break if the set is actually Set<@NonNull X>.

    These considerations are relevant whenever you have to deal with a "legacy" type Set<X> where we have insufficient knowledge: it may be intended as a Set<@NonNull X> or as Set<@Nullable X>. Type checking has to account for both possibilities (clients may rely on either assumption because, e.g., the javadoc could say so).

    Generally, in Java the read vs. write issue is solved by using bounded wildcards: Set<? extends X> ensures that read access will always yield "at least" X (or better). Set<? super X> ensures that the requirement for inserting new elements is "at most" X, i.e., any X or better will be accepted into the set (whatever the real requirements by the actual list are).

    To apply the above to null annotations simply say Set<? extends @Nullable X> to accept a legacy set and support reading from the set (values will be at least @Nullable X, could be better, e.g., @NonNull X). If you need to insert into a legacy set, assign it to a variable of type Set<? super @NonNull X>. This tells the type checker, that a @NonNull X will always be good enough for inserting.

    This is why Set<? extends @Nullable Entry<..> accepts the result of treeMap.entrySet(), which actually has type Set<Entry<@Nullable Integer, @Nullable String>>. Here inner type arguments are fully annotated from the declaration of treeMap, only the toplevel type argument Entry is unspecified, due to legacy signature of entrySet().

    The last mention also hints at the "real" solution for the example: use external annotations to indicate that entrySet() actually returns @NonNull Set<@NonNull Entry<K,V>>. With this none of the wildcard magic is needed.