Optionals
WARNING
This tutorial assumes that you are familiar with lambda expressions. Take a look at the lambda introduction first, if you are not!
💪 Motivation
The Optional class is widely used in Javacord. Basically, every method that might return a null
value will return an Optional in Javacord instead. Optionals help you to avoid NullPointerExceptions
and make it very clear if a method may not have a result. Here's a small example:
The old way of doing it
User user = api.getCachedUserById(123L);
if (user != null) {
user.sendMessage("Hi!");
}
The new way of doing it
api.getCachedUserById(123L).ifPresent(user ->
user.sendMessage("Hi!")
);
You can imagine an Optional
like a box 📦 that may or may not contain a value. Before accessing this value, you have to "unpack" this box first.
📖 Methods
The Optional class has many useful methods which can all be found in the JavaDocs. This tutorial gives a short introduction to the most common ones.
get()
The get
method returns the value of the Optional or throws a NoSuchElementException
if it does not contain a value.
TextChannel channel = api.getTextChannelById(123L).get();
channel.sendMessage("Hi");
DANGER
You should never use this method blindly but only if you are 100% sure the optional contains a value.
Every time you use this method carelessly, a kitten dies 🙀! True story.
isPresent()
The isPresent
methods checks, if the Optional contains a value.
Optional<TextChannel> channel = api.getTextChannelById(123L);
if (channel.isPresent()) {
// A text channel with the id 123 exists. It's safe to call #get() now
channel.get().sendMessage("Hi");
}
orElse(...)
The orElse
methods returns the value of the Optional if it is present. Otherwise, it returns the given default value.
// The user may not have a nickname on the given server.
// In this case, we use the user's "regular" name.
String displayName = user.getNickname(server).orElse(user.getName());
The example above is (mostly) equivalent to the example below but much more concise.
String displayName = "";
Optional<String> nickname = user.getNickname(server);
if (nickname.isPresent()) {
displayName = nickname.get();
} else {
displayName = user.getName();
}
TIP
In this case you can just use user.getDisplayName(server)
instead.
ifPresent(...)
The ifPresent
method is very similar to an if (value != null) { ... }
check. It takes a Consumer as it's argument. This consumer is called if the Optional contains a value. Together with lambda expressions this can be a very handy method.
api.getTextChannelById(123L).ifPresent(channel -> {
channel.sendMessage("Hi!");
});
The example above is (mostly) equivalent to the example below but more concise.
Optional<TextChannel> channel = api.getTextChannelById(123L);
if (channel.isPresent()) {
channel.get().sendMessage("Hi!");
}
filter(...)
The filter
method filters the Optional for a given criteria.
Optional<User> botUser = api.getCachedUserById(123L).filter(User::isBot);
The example above is equivalent to the example below but more concise.
Optional<User> user = api.getCachedUserById(123L);
Optional<User> botUser;
if (user.isPresent() && user.get().isBot()) {
botUser = user;
} else {
botUser = Optional.empty();
}
map(...)
The map
method "converts" the type of an Optional. This is useful, if the type of an Optional does not contain the final value you need.
The following example gets the name of the bots current activity (the "Playing xyz" status) or "None" if the bot has no current activity.
String activityName = api.getYourself().getActivity().map(Activity::getName).orElse("None");
For better understanding, here's the exact same code but with the types as comments:
String activityName = api.getYourself() // User
.getActivity() // Optional<Activity>
.map(Activity::getName) // Optional<String>
.orElse("None"); // String
flatMap(...)
The flatMap
method if very similar to the map
methods. It is used to map values that itself are Optionals to prevent Optional nesting (a "box in a box").
String activityName = api.getCachedUserById(123L) // Optional<User>
.flatMap(User::getActivity) // Optional<Activity>
.map(Activity::getName) // Optional<String>
.orElse("None"); // String
Without flatMap
, the code would look like this:
String activityName = api.getCachedUserById(123L) // Optional<User>
.map(User::getActivity) // Optional<Optional<Activity>>
.filter(Optional::isPresent) // Optional<Optional<Activity>>
.map(Optional::get) // Optional<Activity>
.map(Activity::getName) // Optional<String>
.orElse("None"); // String
📚 Further Read
This tutorial only focuses on the absolute basics. For an in-depth introduction to Optionals, you can take a look at Oracle's article about optionals.