java.util.Optional fields

Remi Forax forax at univ-mlv.fr
Fri Sep 21 08:30:54 PDT 2012


On 09/21/2012 04:51 PM, Brian Goetz wrote:
>>> An alternative of throwing NoSuchElementException was replaced by
>>> returning an Optional.
>> Having Optional enables me to do fluent API thingies like:
>>     stream.getFirst().orElseThrow(() -> new MyFancyException())
>>
>> I would speculate *this* was the intended goal for Optional.
> Absolutely.
>
> Take the example from SotL/L from the reflection library.
>
> Old way:
>
> for (Method m : enclosingInfo.getEnclosingClass().getDeclaredMethods()) {
>        if (m.getName().equals(enclosingInfo.getName()) ) {
>            Class<?>[] candidateParamClasses = m.getParameterTypes();
>            if (candidateParamClasses.length == parameterClasses.length) {
>                boolean matches = true;
>                for(int i = 0; i < candidateParamClasses.length; i++) {
>                    if
> (!candidateParamClasses[i].equals(parameterClasses[i])) {
>                        matches = false;
>                        break;
>                    }
>                }
>
>                if (matches) { // finally, check return type
>                    if (m.getReturnType().equals(returnType) )
>                        return m;
>                }
>            }
>        }
>    }
>
>    throw new InternalError("Enclosing method not found");
>
> Without Optional:
>
> Method matching =
>        Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())
>           .filter(m -> Objects.equals(m.getName(), enclosingInfo.getName())
>           .filter(m ->  Arrays.equals(m.getParameterTypes(),
> parameterClasses))
>           .filter(m -> Objects.equals(m.getReturnType(), returnType))
>           .getFirst();
> if (matching == null)
>       throw new InternalError("Enclosing method not found");
> return matching;
>
> This is much better, but we still have a "garbage varaiable", matching,
> which we have to test and throw before returning.
>
> With Optional:
>
> return
>     Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())
>           .filter(m -> Objects.equals(m.getName(), enclosingInfo.getName())
>           .filter(m ->  Arrays.equals(m.getParameterTypes(),
> parameterClasses))
>           .filter(m -> Objects.equals(m.getReturnType(), returnType))
>           .findFirst()
>           .getOrThrow(() -> new InternalError(...));
>

Focusing on findFirst() is an error in my opinion, the corresponding 
imperative code
is more readable (if written correctly) and also faster.

Here is how it should be written:

for (Method m : enclosingInfo.getEnclosingClass().getDeclaredMethods()) {
   if (m.getReturnType() == returnType &&
       m.getName().equals(enclosingInfo.getName()) &&
       Arrays.equals(m.getParameterTypes(), parameterClasses)) {
     return m;
   }
}
throw new InternalError(...)

The question is where to draw the line, where to use the stream API 
versus the imperative one.
The stream API is interesting when map/reduce/groupBy or flatMap (etc.) 
are involved,
something that require local variables in their imperative counterparts 
because the stream version
in that case more straightforward to read.

cheers,
Rémi



More information about the lambda-dev mailing list