1

I'm trying to learn how to use Optional in Java - but this doesn't seem correct what I'm doing.

User user = null;

public AuthCheck(User user) throws Exception {
    if (user == null) {
        throw new Exception("No user!");
    }
    if (!(user.getStuff() != null && !user.getStuff().isEmpty())) {
        throw new Exception("User has no stuff");
    }
    this.user = user;
}

This is how I tried to convert it using Optional:

Optional<UserOptional> user;

public AuthCheckOptional(Optional<UserOptional> user) throws Exception {
    user.orElseThrow(() -> new Exception("No User!"));

    user.map(UserOptional::getStuff)
        .orElseThrow(() -> new Exception("User has no stuff"));

    this.user = user;
}

I would of thought I wouldn't need two separate checks. Also I believe I've changed the logic here as the IsEmpty() isn't happening.

Andrew Tobilko
  • 48,120
  • 14
  • 91
  • 142
Taobitz
  • 546
  • 3
  • 10
  • 22
  • 4
    Why does the function even take an `Optional` if it's a required value and all it does is throw if it's empty? – Matti Virkkunen Apr 28 '18 at 10:34
  • 3
    Passing `Optional` as a parameter isn't useful. Normally, you use `Optional`s to be `null`-safe, but in your example, you would have to check wheter the `Optional` itself is `null`, otherwise you would get a `NullPointerException`. You could use [`Objects.requreNonNull(...)`](https://docs.oracle.com/javase/10/docs/api/java/util/Objects.html#requireNonNull(T)) instead. – Turing85 Apr 28 '18 at 10:38
  • 1
    I think the second check about the user’s stuff can’t manage like this. But in your UserOptional class the methode getStuff can return an optional. And you have user.getStuff().orElseThrow(...) – mickaelw Apr 28 '18 at 10:38
  • 2
    Given that you want different error messages depending on the situation, there is no way to avoid two checks in some way. – Joe C Apr 28 '18 at 10:41
  • 1
    Are you carrying out all those checks in the correct place? Let me ask you this: Is a `User` whose `getStuff` method returns `null` allowed anywhere in your program? Is a user's "stuff" allowed to be empty anywhere in your program? Does `User` have to be mutable? If not, make `User` immutable and enforce those invariants at instantiation of a `User`, not at instantiation of an `AuthCheck`. – jub0bs Apr 29 '18 at 19:38
  • 2
    Besides, as a rule of thumb, methods shouldn't take `Optional` parameters. – jub0bs Apr 29 '18 at 19:41

4 Answers4

6

You don't want to use Optional as an input parameter; it's an anti-pattern. Check out this article on DZone.

What you can do to improve your code is as follows:

User user = null;

public authCheck(User user) {
    Objects.requireNonNull(user, "No user!");

    if (Objects.requireNonNull(user.getStuff(), "User has no stuff").isEmpty()) {
        throw new RuntimeException("User has no stuff");
    }

    this.user = user;
}

(Method names should start with a lower case in Java.)

You could further condense it, but the question is whether the code would be any clearer.

There's nothing wrong with null, if used properly. It means "undefined", and we don't have non-null object references in Java, like there are in Kotlin or Scala.

But perhaps you could think a little bit about how User objects are created, so that you avoid this issue altogether. You could perhaps employ the Builder design pattern. More often than not, rethinking your code can avoid situations like these.

SeverityOne
  • 2,476
  • 12
  • 25
  • 2
    Thanks for the DZone, I definitely would have broken a few of those anti-patterns! I didn't even know Objects.requireNonNull existed. – Taobitz Apr 29 '18 at 08:31
  • Objects.requireNonNull throws a NullPointerException if user is null so the if statement and throw are unnecessary – Encaitar May 04 '18 at 15:13
  • I encourage you to read the JavaDocs and look closely at my code. The `if` statement is not a null check. – SeverityOne May 04 '18 at 16:00
4

I would like to warn you that it's insane and so confusing. And I see no reason to re-write the first good-looking approach*.

But if you really want to play with Optional, here's a method chain**:

Optional.of(
        Optional.ofNullable(user)
                .orElseThrow(() -> new Exception("No user!"))
)
        .map(User::getStuff)
        .filter(s -> !s.isEmpty())
        .orElseThrow(() -> new Exception("User has no stuff"));

*There is room for improvement as well. !(!a && !b) simply means a || b.

final String stuff = user.getStuff();
if (stuff == null || stuff.isEmpty()) {
    throw new Exception("User has no stuff");
}

**I assumed that getStuff returns a String.

Andrew Tobilko
  • 48,120
  • 14
  • 91
  • 142
  • 1
    after so much time with java-8, I actually prefer this approach. seriously. 1+ – Eugene Apr 28 '18 at 18:37
  • 1
    Thanks @Andrew , Yea it was purely for practice/learning, I would definitely agree that I made it a lot more convoluted! Thanks for the tip/help on both! – Taobitz Apr 29 '18 at 08:26
  • @Eugene seriously? I think it speaks a lot that nobody noticed that it is actually wrong. If `getStuff` returns `null`, the code throws with `"No user!"` instead of `"User has no stuff"`. I have to agree with Andrew that it’s “insane and confusing”… – Holger May 02 '18 at 09:19
  • @Holger I did not look at the code very much, just at the approach actually – Eugene May 02 '18 at 09:23
  • @Eugene no contradiction. It took me less than five seconds to see that the `if` statement does the right thing (“did not look at the code very much”), but I had to look carefully at the nested optionals, for half a minute, to recognize that it does not seem to do the right thing, but in the end, I made an actual test, to be sure. So I prefer the code I don’t need to look that carefully… – Holger May 02 '18 at 09:27
  • @Holger yes, there was a mistake, thanks (even the author didn't notice it) :) – Andrew Tobilko May 02 '18 at 14:37
2

You're using the Optional<T> type incorrectly and therefore you'll gain no benefit, rather it makes your code harder to understand and maintain.

You should also avoid passing Optional<T> as a method parameter, see here.

The non-optional approach is better for this type of logic and hence I'd stress that you proceed with that approach.

As @Cay S. Horstmann once mentioned in his book:

The key to using Optional effectively is to use a method that either consumes the correct value or produces an alternative.

Ousmane D.
  • 54,915
  • 8
  • 91
  • 126
  • 1
    Thanks Anomine - I struggle to actually see when I would use Optional based on this and the AntiPattern Dzone article linked above. I understand why/when not to use it now but no idea when I should :) – Taobitz Apr 29 '18 at 08:39
  • 3
    @KarlBoyle The block quote in my answer tells you when you should use optional. maybe you can watch this video from Stuart Marks which gives an excellent explanation on what to avoid and when to use it. https://youtu.be/Ej0sss6cq14 – Ousmane D. Apr 29 '18 at 10:26
0

I'd suggest a few restructuring to use Optionals more effectively.

  1. You should avoid calling the AuthCheck method with a null user in the first place, but you should have an Optional if it is null somewhere else
Optional<User> user = someWayToMaybeGetAUser();
user.map(AuthCheck::new)
  1. AuthCheck should probably just take in a User and a Stuff (and save both as independent fields).
public AuthCheck(User user, Stuff stuff) {
   this.user = user;
   this.stuff = stuff;
}

Then apply rule 1 again, and just don't pass in a null user and stuff to the the authcheck.

Optional<User> user;
Optional<Stuff> stuff = user.flatMap(User::getStuff);
Optional<AuthCheck> = user.flatMap(u -> stuff.map(s -> new AuthCheck(u, s));
// this is where using a more functional java library can help because you can use helper functions instead of the nested flatmaps

// Ex from cyclops
Option<AuthCheck> = user.forEach2(u -> stuff, AuthCheck::new); 
// Ex from vavr
For(user, stuff).yield(AuthCheck::new);

so then we can update the constructor to

public AuthCheck(User user, Stuff stuff) {
   this.user = Objects.requireNonNull(user);
   this.stuff = Objects.requireNonNull(stuff);
}

this keeps our data flow outside of the object, but still prevents incorrect authchecks from being created. Now, if you end up with a null being passed in, it's a programmer error.

Finally, the client code has a choice of what to do with an Optional authcheck if it doesn't exist, and if you want to throw specific exceptions arbitrarily you can add them wherever you want in the chain. Notice how forcing the data dependency avoids the problem with the other answer where chaining user into stuff loses the reason that the optional is empty (you can't decide at the end of the chain if user or stuff was the reason it was null).

User user = getMaybeUser().orElseThrow(() -> new Exception("No user"));
Stuff stuff = user.getStuff().orElseThrow(..."No stuff");
AuthCheck ac = new AuthCheck(u, s);

There's a bunch of ways to structure this, so it's more up to you.

akfp
  • 42
  • 2