I'd suggest a few restructuring to use Optionals more effectively.
- 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)
- 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.