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.

6
  • 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? Commented Apr 28, 2018 at 10:34
  • 3
    Passing Optional as a parameter isn't useful. Normally, you use Optionals 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(...) instead. Commented Apr 28, 2018 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(...) Commented Apr 28, 2018 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. Commented Apr 28, 2018 at 10:41
  • 2
    Besides, as a rule of thumb, methods shouldn't take Optional parameters. Commented Apr 29, 2018 at 19:41

4 Answers 4

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.

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks for the DZone, I definitely would have broken a few of those anti-patterns! I didn't even know Objects.requireNonNull existed.
Objects.requireNonNull throws a NullPointerException if user is null so the if statement and throw are unnecessary
I encourage you to read the JavaDocs and look closely at my code. The if statement is not a null check.
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.

6 Comments

after so much time with java-8, I actually prefer this approach. seriously. 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!
@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 I did not look at the code very much, just at the approach actually
@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…
|
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.

2 Comments

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 :)
@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. youtu.be/Ej0sss6cq14
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.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.