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?
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?