Optional<@NonNull T>

16 views
Skip to first unread message

Jens Bannmann

unread,
Jul 18, 2024, 1:06:56 PM (9 days ago) Jul 18
to jspecify-discuss
Hi,

I'm nullmarking one of my libraries, and it has the following method:

/**
 * Gets the value to which the given key is mapped, if existing. <br>
 * <br>
 * Unlike {@link Map#get(Object)}, with this method the compiler verifies the type of the key argument. Also, as it
 * returns an {@link Optional}, calling code can use chained method calls instead of separate {@code if} blocks.
 *
 * @return a present {@link Optional} if the given map contains a non-{@code null} value for the given {@code key}.
 */
public <K, V> Optional<V> tryGet(Map<K, V> map, K key)
{
    return Optional.ofNullable(map.get(key));
}

This is the annotated signature I came up with:

public <K extends @Nullable Object, V extends @Nullable Object> Optional<V> tryGet(Map<K, V> map, K key)

Deciding on the annotations for the type parameters and the method arguments felt fairly straightforward. But I'm less sure about the return type of tryGet(): As Optional.get() etc will in fact never return a null value, my gut feeling would be to declare the return type as Optional<@NonNull V>. However, this is probably redundant (or wrong?) as the JDK nullness annotation overlay(?) by JSpecify might already add @NonNull to relevant methods of the Optional class.

So, my question is: what's the correct declaration here?

Also, did I miss something or is this case not mentioned in the user guide? (Not saying it should; you guys are the experts here.)

Kind regards,
Jens

P.S.: Congratulations to the 1.0.0 release, and a big thank you for your hard work leading to it!

Chris Povirk

unread,
Jul 18, 2024, 1:28:56 PM (9 days ago) Jul 18
to Jens Bannmann, jspecify-discuss
Hi, Jens,

I'd probably go with your signature with your suggested tweak to use Optional<@NonNull V>.

We've discussed classes like Optional a bit. The principle we've been leaning toward is that, because there aren't "two different kinds" of Optionals ("those that might contain null" and "those that can't"), we don't want it to be possible to have both an Optional<String> and an Optional<@Nullable String> because then you might find yourself with one of kind when an API requires the other. This is in contrast to types like List, where List<String> (can't put nulls in, won't get nulls out) and List<@Nullable String> (can put nulls in, might get nulls out) are usefully different. You can see our current leaning encoded in this choice of annotations for Optional in our in-progres repo of JDK annotations (which aren't used by anything public except the reference checker at the moment and which haven't been subject to the level of scrutiny of the org.jspecify annotations themselves).

(If you're really curious, you can peek at https://github.com/jspecify/jspecify/issues/78. But that, like most of our issue threads, does not make for a good read. Someday we will add an explanation to the wiki or otherwise document it somewhere beyond this email. Maybe I'll copy and paste this there now....)

Given that we've declared Optional to follow that principle, users would need to use Optional<@NonNull V> when the type parameter V allows nullable values. We actually threw in an example of that in the User Guide, but its purpose there was more to have a realistic example for @NonNull than it was to actually prepare users for this particular situation.

(If you're really really curious, you might even be interested in a similar API that we came across recently. In the linked thread, I observe that we could consider an alternative that would be analogous to "public <K extends @Nullable Object, V> Optional<V> tryGet(Map<K, ? extends @Nullable V> map, K key)." I think that either approach could work, and I haven't found compelling reasons to suggest one over the other—nor compelling reasons to even write up the discussion in a more understandable way :))

Regards,
Chris
Reply all
Reply to author
Forward
0 new messages