Back to Home

Optional: Schrödinger Cat in Java 8

java · optional · java 8

Optional: Schrödinger Cat in Java 8

  • Tutorial
Imagine that in a box are a cat, a radioactive substance and a flask with hydrocyanic acid. There are so few substances that only one atom can decay in an hour. If it breaks up within an hour, the reader discharges, a relay trips, which activates the hammer, which breaks the flask, and the cat will get a karachun. Since the atom may decay, or may not decay, we do not know whether the cat is alive or not, therefore it is both alive and dead. This is a thought experiment called the Schrödinger Cat.



The Optional class has similar properties - when writing code, the developer often cannot know whether the necessary object will exist at the time of program execution or not, and in such cases it is necessary to do null checks. If such checks are neglected, then sooner or later (usually early) your program will crash with a NullPointerException.

Colleagues! The article, like any other, is not perfect and can be amended. If you see the possibility of a significant improvement in this material, indicate it in the comments.

How to get an object through Optional?


As already mentioned, the Optional class may contain an object, or it may contain null. For example, let's try to extract a user with a given ID from the repository:

User = repository.findById(userId);

Perhaps the user with this ID is in the repository, but maybe not. If there is no such user, a NullPointerException arrives to us in the stack trace. If we didn’t have the Optional class in reserve, we would have to invent some such construction:

User user;
if (Objects.nonNull(user =  repository.findById(userId))) {
(остальная борода пишется тут)
}

Agree, not really. It is much nicer to deal with this line:

Optional user = Optional.of(repository.findById(userId));

We get an object in which the requested object may be - or it may be null. But with Optional we need to somehow work further, we need the entity that it contains (or does not contain).

There are only three categories of Optional:

  • Optional.of - Returns an Optional object.

  • Optional.ofNullable - returns an Optional object, and if there is no generic object, returns an empty Optional object.

  • Optional.empty - Returns an empty Optional object.

There are also two methods arising from knowing whether a wrapped object exists or not - isPresent () and ifPresent ();

.ifPresent ()


The method allows you to perform some action if the object is not empty.

Optional.of(repository.findById(userId)).ifPresent(createLog());

If we usually perform some action when the object is absent (more on that below), then here it is just the opposite.

.isPresent ()


This method returns the answer whether the sought object exists or not, in the form of a Boolean:

Boolean present = repository.findById(userId).isPresent();

If you decide to use the get () method described below, then it will not be superfluous to check whether the given object exists using this method, for example:

Optional optionalUser = repository.findById(userId);
User user = optionalUser.isPresent() ? optionalUser.get() : new User();

But such a design personally seems cumbersome to me, and we will talk about more convenient methods for obtaining the object below.

How to get the object contained in Optional?


There are three direct methods for further obtaining an object of the orElse () family; As follows from the translation, these methods work if the object in the received Optional was not found.

  • orElse () - returns the object by default.

  • orElseGet () - Calls the specified method.

  • orElseThrow () - throws an exception.

.orElse ()


Suitable for cases when we definitely need to get an object, even if it is empty. The code, in this case, may look like this:

User user = repository.findById(userId).orElse(new User());

This design is guaranteed to return us an object of class User. It helps a lot at the initial stages of cognition of Optional, as well as, in many cases, associated with the use of Spring Data JPA (there most of the classes of the find family return exactly Optional).

.orElseThrow ()


Very often, and again, in the case of using Spring Data JPA, we need to explicitly declare that there is no such object, for example, when it comes to an entity in the repository. In this case, we can get the object or, if it is not, throw an exception:

User user = repository.findById(userId).orElseThrow(() -> new NoEntityException(userId));

If the entity is not detected and the object is null, a NoEntityException will be thrown (in my case, a custom one). In my case, the line “User {userID} not found. Check the request details. ”

.orElseGet ()


If the object is not found, Optional leaves space for “Option B” - you can execute another method, for example:

User user = repository.findById(userId).orElseGet(() -> findInAnotherPlace(userId));

If the object was not found, it is suggested to search elsewhere.

This method, like orElseThrow (), uses Supplier. Also, through this method, you can, again, call the default object, as in .orElse ():

User user = repository.findById(userId).orElseGet(() -> new User());

In addition to methods for obtaining objects, there is a rich toolbox for transforming an object that is morally inherited from stream ().

Work with the received object.


As I wrote above, Optional has a good toolkit for transforming the resulting object, namely:

  • get () - returns the object, if any.

  • map () - converts an object to another object.

  • filter () - filters the contained objects by predicate.

  • flatmap () - returns a set in the form of a stream.

.get ()


The get () method returns an object wrapped in Optional. For instance:

User user = repository.findById(userId).get();

A User object will be retrieved, wrapped in Optional. Such a construction is extremely dangerous because it bypasses the null check and makes the use of Optional itself meaningless, since you can get the desired object, or you can get NPE. This design will have to be wrapped in .isPresent ().

.map ()


This method completely repeats the similar method for stream (), but only works if Optional has a non-null object.

String name = repository.findById(userId).map(user -> user.getName()).orElseThrow(() -> new Exception());

In the example, we got one of the fields of the User class, packed in Optional.

.filter ()


This method is also borrowed from stream () and filters the elements by condition.

List users = repository.findAll().filter(user -> user.age >= 18).orElseThrow(() -> new Exception());

.flatMap ()


This method does exactly the same as the stream one, with the only difference being that it only works if the value is not null.

Conclusion


The Optional class, when used skillfully, greatly reduces the application's ability to crash with a NullPoinerException, making it more understandable and compact than if you were doing countless null checks. And if you use popular frameworks, then you will have to study this class in-depth, since the same Spring drives it both in the tail and in the mane in its methods. However, Optional is the acquisition of Java 8, which means that to know it in 2018 is simply a must.

Read Next