I would like to offer some accessors to a value-container (that is a java.lang.Map under the hood).
When implementing accessors like this
public Optional<Integer> getSomeValueThatMayBeNullOrAnInteger() {
Integer nullable = this.get("keyToANullableInteger", Integer.class); // is casting to given Class
return Optional.ofNullable(nullable);
}
... 'ofNullable' is marked with 'unsafe null type convertion'.
Why is this? The parameter ofNullable is not marked as @NonNull. Is it, because empty() or of() is used and of() is checking for NonNull? Is this a Eclipse-bug, because empty() is ignored, when checking the possibilities?
Can someone please explain this to me and maybe tell me, if there is another way to solve this warning as using @SuppressWarnings("null"). Thanks
Every type has a nullity status. Thus, in Optional<Integer>
, both types mentioned have a nullity status - is it @NonNull Optional<@NonNull Integer>
? Presumably the answer is trivially: Yes.
There is no point in having a 'nullable optional' (if you have those, stop and fix that first), and the typearg to optional is trivially @NonNull
(you can't have an Optional.SOME whose value is null
).
However, eclipse doesn't know that. Furthermore, the java core classes are not nullity annotated.
Presumably, you've marked your class as 'default to -everything is nonnull-' otherwise you need to slap a @NonNull
on everything which is unwieldy. Thus, eclipse thinks your method's return type is @NonNull Optional<@NonNull Integer>
.
However, the Optional.ofNullable
method (in class java.util.Optional
), simply returns @WhoKnows Optional<@WhoKnows T>
- the j.u.Optional class is not marked as 'default to everything being non null' nor is the method itself annotated with @NonNull
because the java core classes do not have such annotations at all. Therefore, eclipse is warning you, telling you: Hey, Optional.ofNullable
might return null
(we know it won't, but how would eclipse know that?), and you're just returning it verbatim, but your method promises to never return null
so this is a potential problem.
You need a way to have your nullity checker (eclipse, in your case) know the nullity status of everything. Including libraries you are using, so, at least including java.*
. That means you need a concept called 'external annotations' - a file that 'fills in the blanks', so to speak, on java.*
and everything else. For starters, it would list that the ofNullable
static method in java.util.Optional
should be deemed to be annotated with @NonNull
.
Without such a system it's a miserable, miserable experience - going halfway on nullity annotations means you get an endless cavalcade of the very warnings you are getting now. Leading you to remove the warnings (which makes the feature pointless) or annotate most of your methods with @SuppressWarnings("null")
which would also make the feature pointless.
The last time I checked which is admittedly a very long time ago, there was no such file or system available, but I believe eclipse has at least added the concept of 'external annotations'. I'd search the web and try to find an EA file that includes all/most of the java.* API at least and use it.
Optional
is a way to lift nullity status into the type system.
Annotations are also a way to do that.
Do not use both at the same time.
Optional
is not backwards compatible. Take java.util.Map
's .get()
method. It currently returns V
. If you feel Optional is the correct answer, then this method is incorrect as it should be returning Optional<V>
instead. However, changing it is backwards incompatible, and java is exceedingly unlikely to ever do that. Thus, Optional
will never get to their perfect world, where Optional is used as de-facto mechanism everywhere.@PolyNull
concept (only checkerframework's does). That means they are all incapable of representing the nullity status of certain existing methods.Optional
will never be able to fully replace null
in java.Nullity annotations are unwieldy (not standardized, lacking external annotation files). Optional
is a pipe dream (existing APIs cannot be modified to use it). So where does that leave you?
Mostly, just accept that null
is fine. Turn off the annotation-based checks, stop using Optional
for stuff like this.
There is something you can do right now, and something that the java API is already doing, as it's entirely backwards compatible:
_Work on nicer APIs.
Have a look at the javadoc of java.util.Map
, for example. There's computeIfAbsent
, getOrDefault
, and a few other methods that mostly mean you don't need any type-based nullity at all to work with Map without having to bother with null
. For example, you don't have to write this code:
Map<String, List<String>> example = ...;
List<String> list = example.get(key);
if (list == null) {
list = new ArrayList<>();
example.put(key, list);
}
list.add("Test");
Instead you can just write:
Map<String, List<String>> example = ...;
example.computeIfAbsent(key, k -> new ArrayList<>()).add("Test");
and you don't have to write:
Map<String, Integer> example = ...;
int v = 0;
// 2 calls - ugly
if (example.containsKey(key)) v = example.get(key);
// or:
Integer vRaw = example.get(key);
if (vRaw != null) v = vRaw.intValue();
You can instead just write:
Map<String, Integer> example = ...;
int v = example.getOrDefault(key, 0);
You can apply the same principles to the code you write. For example:
public int getSomeValue(int defaultValue) {
Integer nullable = this.get("keyToANullableInteger", Integer.class);
return nullable == null ? defaultValue : nullable.intValue();
}
public Integer getSomeValueOrNull() {
return this.get("keyToANullableInteger", Integer.class);
}
public int getSomeValue() {
Integer n = this.get("keyToANullableInteger", Integer.class);
if (n == null) throw new NullPointerException("key not present in data store");
return n.intValue();
}
No chance a caller is going to be confused about what can and cannot be null here. (You probably don't need all 3, think a bit about your API design).