2

I recently started updating my Java projects with Eclipse's nullability annotations. I have a JavaFX base project, containing some translation classes.

Now, in my LocalizedList, I initialize it with an element in the document tree and it recursively adds all its subelements.

@NonNullByDefault
private void locChildren(Styleable c) {
    String localizable = getKey(c);
    if(localizable != null) {
        backingMap.put(c, localizable);
        setText(c, localizable);
    }
    if(c instanceof MenuBar) {
        MenuBar mb = (MenuBar)c;
        initLoc(mb.getMenus());
    } // else if ...
}

@NonNullByDefault
public void initLoc(List<? extends Styleable> s) {
    for(Styleable c : s) {
        locChildren(c);
    }
}

Now, if I left it with just this, I get the awfully long warning message

Null type safety (type annotations): The expression of type 'ObservableList<Menu>' needs unchecked conversion to conform to '@NonNull List<? extends @NonNull Styleable>', corresponding supertype is 'List<Menu>'

This is because MenuBar#getMenus() is not annotated with any nullability annotations, and to be expected.


After applying the @Nullable annotation to the List itself, the problem was not resolved. So, I added @Nullable to the wildcard. This is where I stumbled upon something confusing.

@NonNullByDefault
public void initLoc1(@Nullable List<@Nullable ? extends Styleable> s) {
    for(Styleable c : s) {
        locChildren(c);
    }
}

@NonNullByDefault
public void initLoc2(@Nullable List<@Nullable ? extends @Nullable Styleable> s) {
    for(Styleable c : s) {
        locChildren(c);
    }
}

@NonNullByDefault
public void initLoc3(@Nullable List<? extends @Nullable Styleable> s) {
    for(Styleable c : s) {
        locChildren(c);
    }
}

Each of these three declarations is valid and compiles fine, however only the last one makes the warning message disappear.


I would have expected the first one to be valid, as it actually annotates the "type" that is used in the method itself, and am completely confused by the second example.

What exactly is the semantic difference between these three declarations, and why does three work, while two and one do not?

Adowrath
  • 701
  • 11
  • 24
  • 2
    I'm a bit surprised that the compiler accepts type annotations on the wildcard at all. It is not a type. Are you certain that the warnings you see in the first two cases are not related to *that*? It might not be hard to focus on the fact that you still get warnings, and overlook the fact that they are different warnings. – John Bollinger Jul 12 '16 at 22:10
  • @John I am not sure whether I understood you correctly, but the three error messages (no annotation, wildcard annotated, both annotated) are distinct, but just in that they change the List<...>-type: `List extends @NonNull Styleable>` (as by the `@NonNullByDefault` annotation), `List<@Nullable ? extends Styleable>` and `List<@Nullable ? extends @Nullable Styleable>`. The rest of the warnings remain the same. – Adowrath Jul 12 '16 at 22:23
  • @John Although I have not yet found the reasoning, it seems to be an addition to the Java 8 JLS. Compare [JLS 8, 4.5.1](https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.5.1) and [JLS 7, 4.5.1](https://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.5.1) – Adowrath Jul 12 '16 at 22:53
  • 1
    I acknowledge that that the JLS permits the wildcard to be annotated -- thanks, I learned something new today. Nevertheless, the meaning of annotating the wildcard is a function of the annotation and annotation processor. It's not obvious to me what annotating the wildcard *should* mean, and at the moment I'm not prepared to research its docs to figure that out. – John Bollinger Jul 13 '16 at 00:54
  • @John I think I have a start for it. [JEP 104](http://openjdk.java.net/jeps/104) was the initializer for this addition, and supports its idea by "Allow[ing] development of useful pluggable type checkers that refine Java's built-in type system." The wildcard ? is a type, and thus should be annotatable. Interestingly though, `@NonNull ? extends @Nullable X` does not work, although by Liskov, it should: Everything abount a nullable X is true for an existent X - except for being null, of course. Eclipse, however, declares this as a violation of its typesystem. – Adowrath Jul 13 '16 at 10:00
  • 1
    Saying that a wildcard is a type is like saying that (unescaped) `*` is a filename to the shell. Neither is true. Wildcards can by annotated with type annotations not because they are types, but simply because JSR 308 says they can be. But all of that is largely beside the point. JLS 8, reflecting JSR 308, speaks only to what can be annotated and how. It says nothing whatever about what it *means* for a wildcard to be annotated. Any such meaning is specific to annotation processors in use. – John Bollinger Jul 13 '16 at 13:25

1 Answers1

1

To understand the original code example it is necessary to know the exact effect of @NonNullByDefault, which can be fine tuned using enum DefaultLocation. The latter mentions

Wildcards and the use of type variables are always excluded from NonNullByDefault.

On the other hand the wildcard bound extends Styleable is affected by @NonNullByDefault.

This explains why the expected argument type from initLoc is @NonNull List<? extends @NonNull Styleable>. Method initLoc3 resolves the compile error, because it overrides exactly those two @NonNull annotations as shown here - by stating explicit @Nullable in those positions.

As to applying an explicit @Nullable to the wildcard itself, Eclipse follows the concepts as put forward for the Checkers Framework. In particular @Nullable ? is interpreted as having lower and upper bounds with nullable property.

This explains why initLoc2 is different from initLoc3: it defines an additional lower bound which is then unmatched by the actual argument.

In order to solve your problem regarding MenuBar you may want to consider using external annotations.

Disclaimer: I wasn't able to exactly reproduce you situation, because I was lacking the class context of the methods shown. Where do getKey, backingMap and setText come from?

Stephan Herrmann
  • 7,963
  • 2
  • 27
  • 38
  • `setText` and `getKey` are local methods with the signature `void setText(Styleable, String)` and `@Nullable String getKey(Styleable)`. You can leave them with an empty body/a default return. `backingMap` is a simple `Map`. I have read myself into the `DefaultLocation` enum and removed the `TYPE_BOUND` for the method initLoc whilst adding some assertions to keep the conversions to a `@NonNull`. I don't understand though how `initLoc2` is exactly different - if it has a `@Nullable` upper bound by the extends-clause, why does adding a second `@Nullable` break things, exactly? – Adowrath Jul 14 '16 at 10:47
  • Ah, I understand, as `Menu` is not a supertype of `@Nullable Menu`. Thank you for your addition, I have accepted the answer. – Adowrath Jul 14 '16 at 11:29