8

So I have been using Guava's Optional for a while now, and I'm moving to Java 8 so I wanted to use its Optional class, but it doesn't have my favorite method from Guava: asSet(). Is there a way to do this with the Java 8 Optional that I am not seeing. I love being able to treat Optional as a collection so I can do this:

for (final User u : getUserOptional().asSet()) {
   return u.isPermitted(getPermissionRequired());
}

In some cases avoids the need for an additional variable.

For example:

 Optional<User> optUser = getUserOptional();
 if (optUser.isPresent()) {
     return optUser.get().isPermitted(getPermissionRequired());
 }

Is there an easy way to replicate the Guava style in Java 8's Optional?

Thanks

Viktor Nordling
  • 8,694
  • 4
  • 26
  • 23
csyperski
  • 992
  • 3
  • 15
  • 33

6 Answers6

12

There is a simple way of converting an Optional into a Set. It works just like any other conversion of an Optional:

Given an Optional<T> o you can invoke

o.map(Collections::singleton).orElse(Collections.emptySet())

to get a Set<T>. If you don’t like the idea of Collections.emptySet() being called in every case you can turn it into a lazy evaluation:

o.map(Collections::singleton).orElseGet(Collections::emptySet)

however, the method is too trivial to make a performance difference. So it’s just a matter of coding style.

You can also use it to iterate like intended:

for(T t: o.map(Collections::singleton).orElse(Collections.emptySet()))
    // do something with t, may include a return statement
Holger
  • 285,553
  • 42
  • 434
  • 765
  • 4
    @csyperski I don't see why you would want to stick with this empty Set + for loop trick you used with Guava, when you can simply perform the required operation (the one that is currently inside the loop) on the optional, and have a default value if the optional is empty - `return optUser.map(u -> u.isPermitted(getPermissionRequired())).orElse(Boolean.FALSE)`. – Eran Aug 11 '14 at 12:40
  • Because is this case it works, but it doesn't always. Lets say we have the following: public Optional attemptLogin(String email, .... ) { // first lets see if the user is banned for ( final FailedLogin failedLogin : getFailedLogin(email, ipAddress).map(Collections::singleton).orElseGet(Collections::emptySet) ) { logger.warn("Account is currently banned {}", failedLogin); return Optional.empty(); } // perform other login validations }. This may not be the best example, but it is one I just saw in some legacy code. – csyperski Aug 11 '14 at 12:57
  • 1
    @Eran: your solution works if returning a certain fixed value is an alternative. However, if not returning but proceeding with the next statement is desired, it won’t. Still, thinking about a redesign which has not this requirement is recommended. – Holger Aug 11 '14 at 13:30
5

You appear to only be using asSet so you can write a for loop, but that's unnecessary in Java 8. Instead of your code

Optional<User> optUser = getUserOptional();
if ( optUser.isPresent() ) {
    return optUser.get().isPermitted(getPermissionRequired());
}

you could write

getUserPresent().map(optUser -> optUser.isPermitted(getPermissionRequired()))
   .orElse(false);

...or, in many cases, you could use Optional.ifPresent(Consumer<T>).

Louis Wasserman
  • 191,574
  • 25
  • 345
  • 413
2

You can use map :

return optUser.map(u -> u.isPermitted(getPermissionRequired()));

But it would return an Optional<WhateverTypeIsPermittedReturns>.

Reference

public Optional map(Function mapper)

If a value is present, apply the provided mapping function to it, and if the result is non-null, return an Optional describing the result. Otherwise return an empty Optional.

Eran
  • 387,369
  • 54
  • 702
  • 768
  • So in this instance I guess I could just do: return optUser.map(u -> u.isPermitted(getPermissionRequired())).get(); and it would have the came effect. – csyperski Aug 09 '14 at 14:13
  • @csyperski get() throws an exception if the optional has no value, so I don't think that's what you want. – Eran Aug 09 '14 at 14:16
  • @csyperski If you want null in case no value is present, use `.orElse(null)`. – Eran Aug 09 '14 at 14:17
  • but it this case it would return a boolean so calling get would be safe to unwrap the boolean, right? – csyperski Aug 09 '14 at 14:31
  • 2
    @csyperski I think if `isPermitted` returns boolean, map operation would return Optional, so calling get on it would throw an exception if it's empty (i.e. if the original Optional is empty). That's why you should use .orElse, and supply a default value (example : `.orElse(Boolean.FALSE)`). – Eran Aug 09 '14 at 16:02
1

I also don't see a really elegant, built-in way to do this, but would like to propose a solution similar to that of Dici:

public static <T> Set<T> asSet(Optional<T> opt) {
    return opt.isPresent() ?
        Collections.singletonSet(opt.get()) :
        Collections.emptySet();
}

This avoids the creation of a new HashSet instance and the insertion of the optional object. (Note: This is basically the "inlined" version of what Guavas Present and Absent classes do: The Present class returns the singletonSet, and the Absent class returns the emptySet).

The usage would be similar to your original pattern:

for( final User u : asSet(getUserOptional()) ) {
   return u.isPermitted(getPermissionRequired());
}
Marco13
  • 53,703
  • 9
  • 80
  • 159
0

You can modify your getUserOptional so that it returns a Set or implement your own asSet method :

public Set<User> getUserOptional() {
     Optional<User> optUser;
     //your code
     Set<User> result = new HashSet<>();
     if (optUser.isPresent())
         result.add(optUser.get());
     return result;
}

or

public static <T> Set<T> asSet(Optional<T> opt) {
    Set<T> result = new HashSet<>();
    if (opt.isPresent())
        result.add(opt.get());
    return result;
}

I don't know if there is a built-in way to do it however.

Dici
  • 25,226
  • 7
  • 41
  • 82
  • That's what I was afraid of, it's just not as elegant in my mind. To me it makes sense to think of Optionals as a collection of zero or one item and then process them as such. – csyperski Aug 09 '14 at 14:05
0

Another alternative is:

getUserOptional().stream().collect(Collectors.toSet())

I'm not convinced this is better, but just wanted to add it here for completeness.

Viktor Nordling
  • 8,694
  • 4
  • 26
  • 23