Nulls

Brian Goetz brian.goetz at oracle.com
Tue Sep 25 07:48:28 PDT 2012


>> Your disbelief is well-founded, since that's not what is being
>> suggested.  What
>> we're suggesting is that findAny (or, more precisely, the Optional
>> constructor
>> called by findAny) could throw NPE if the stream contains a null, even
>> if the
>> stream also contains a non-null.  So the spec looks more like:
>>
>>    @return an absent Optional if the stream is empty, or a present
>>    optional containing a selected element from the stream
>>    @throw NPE if the selected element of the stream is null
>
> Which does make findAny null-aware. And in a less than helpful
> way, because if a user gets NPE, then they know that the predicate
> DOES hold for at least one element (null).

We could hide behind "its not the op throwing, its the Option", and 
compare it to .into(nullHostileCollection).  But, even not trying to 
hide, I'm not sure its null-aware as much as simply null-inappropriate 
-- i.e., don't use this method if you have nulls in your streams -- 
which means that the user does has to be null-aware.  Which I think is 
your point -- the user has to reason about it.

> As usual, my main concern is about impact on composition
> (aka modular reasoning). Any general-purpose
> higher-level utility using findAny without knowing
> if the source may include nulls will need to do
> something like:
>
>    boolean present;
>    T x;
>    try {
>         Optional<T> r = ...findAny(...);
>         if (present = r.isPresent()) x = r.get();
>    } catch(NPE ex) {
>        present = true;
>        x = null;
>    }
>
> Not very nice.

Or more nice:

    ....filter(o -> o != null).findAny();

> I hate to be a pest about this, but the only choices
> I know that compose at all remain:
>
> 1. All stream ops throw NPE on any null element
> 2. All stream ops ignore nulls.
> 3. No use of Optionals; rely only on valueIfAbsent constructions
>
> And of these, choice (2) still seems most defensible.

Not trying to be clever, but I do think that the behavior we are 
proposing *is* ignoring nulls.  It just that the findAny is fused to 
another abstraction (Optional) that is null-hostile.

I could get behind (1), but the only real justification for it at this 
point is "eat your vegetables, they're good for you."  Which seems a 
little heavy-handed.

At this point it seems your primary objection is that because findXxx 
(and possibly other methods) are fused to Optional, we have a collision 
of worlds, and the user has to keep track of the demarcation between the 
null-friendly world and the null-hostile world.  The choices are:

  - Accept a slightly more complex user model (the user has to reason 
about which ops are null-safe)
  - Distort Optional to be null-friendly (I think this has been roundly 
rejected)
  - Eliminate the Optional-bearing ops (Tim would call this punishing 
the innocent)
  - Distort streams to be null-absorbing (This is your (2) above)
  - Make streams null-hostile (your (1) above)

But there's no free lunch -- there's distortion and complexity 
everywhere, and the user *still* has to keep track of the demarcation no 
matter what we do.  (If we choose (2), the user may be surprised to 
wonder "where did my nulls go?  why did my size change?".)

I guess I still prefer the "make the model slightly more complex to 
reason about", as long as we can make all that complexity fall at the 
feet of the null lovers -- which the current proposal does.  After all, 
if you've got nulls in your collection, you *already* have to reason 
about "I can use FooList as a target but not BarList".  Adding "I can't 
use Optional as a target" to that list of things to reason about does 
not seem to make it qualitatively harder.  And putting all the 
complexity on those who have nulls means those who don't use nulls get 
everything they want -- including a simple and safe model.



More information about the lambda-libs-spec-experts mailing list