Разбираем класс java.util.Optional

В этом посте мы рассмотрим класс Optional<T> добавленный в JDK 8. Этот класс призван чтобы спасти нас от NullPointerException дальше будет писаться как NPE и от проверок на null.

Давайте для начала определим что это такое Optional - это класс оболочка, которая внутри себя содержит некоторое значение которое может быть NULL и если  это значение является NULL'ом, может предпринять какие-то действия, например бросить исключение или подставить значение по-умолчанию.

Класс Optional нельзя создать прямым образом через new. Он имеет приватный конструктор. Но для его создания существуют статические методы. Рассмотрим их список.

Optional.empty()

Returns an empty Optional instance.

Этот метод создает пустой класс Optional. Приведу пример.

public Optional<String> getClient() {
return Optional.empty();
}

Что здесь происходит? Здесь просто создается метод getClient() который возвращает Optional<String>, но так как у нас нет имплементации этого метода, то мы просто возвращаем Optional.empty(), вернется Optional<String> но внутри он будет содержать значение null.

Optional.of(T value)

Returns an Optional with the specified present non-null value.

Создает опционал с определенно не null значением. Этот метод нужно использовать только тогда, когда вы уверены что значение не является null. Если вы попробуете сделать следующий вызов

public Optional<String> getClient() {
return Optional.of(null);
}

То данный код скомпилируется, но при попытке запуска все сломается.

Optional.ofNullable(T value)

Returns an Optional describing the specified value, if non-null, otherwise returns an empty Optional.

Этот метод можно использовать в ситуации когда значение может быть null, а может и не быть. Рассмотрим пример.

public Optional<Client> getClient(String username) {
Client client = Client.find()
.where()
.eq("username", username)
.fetchUnique();

return Optional.ofNullable(client);
}

Этот метод ищет клиента и возвращает его как опционал. Мы можем ничего не найти в базе данных и нам вернется null. Мы его можем передать в опционал. И все будет работать. Именно этот опционал и является самым часто используемым. Стоит рассмотреть его внутреннее строение.

 public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}

Он получает значение, смотрит: если переданное значение это null то возвращаем пустой опционал, если значение не null, то создаем объект со значением через Optional.of(T value);

Пора рассмотреть методы конкретного экземпляра Optional.

Дальше мы рассмотрим методы конкретного объекта optional.

filter(Predicate<? super T> predicate) 

If a value is present, apply the provided Optional-bearing mapping function to it, return that result, otherwise return an empty Optional.

Этот метод дает возможность отфильтровать значение опционального типа. Мы передаем ему в параметр Predicat (это стандартный функциональный интерфейс) , он возвращает булево значение, если значение true, то возвращается этот же опционал, если значение false, то возвращается пустой опционал (Optional.empty()), пример.

public Optional<Client> getClient(String username) {
Client client = loadClient(username);

return Optional.ofNullable(client)
.filter((client) -> client.getAge() > 18);
}

В этом методе мы загружаем клиента и закидываем его в optional. Потом проверяем что клиенту больше 18. И возвращаем этот опционал в результате метода.

flatMap(Function<? super T,Optional<U>> mapper)

If a value is present, apply the provided Optional-bearing mapping function to it, return that result, otherwise return an empty Optional.

Этот метод можно применить для изменения значения установленного в опционале. Функция которая передается в метод flatMap должна возвращать опционал. Если значение не установлено, вернется Optional.empty().  P.S Function - это стандартный функциональный интерфейс Java. Давайте усовершенствуем наш предыдущий метод и добавим туда бесполезный функционал.

public Optional<Client> getClient(String username) {
Client client = loadClient(username);

return Optional.ofNullable(client)
.filter((client) -> client.getAge() > 18)
.flatMap((client) -> Optional.of(client.setSolvent(true)));
}

Здесь мы добавили flatMap и если клиент есть в опционале, то устанавливаем ему setSolvent (статус платежеспособности) в true. P.S класс client в методах сеттерах поддерживает fluent interface, по-этому мы здесь возвращаем setSolvent как результат, этот метод возвращает this обхекта client.

get()

If a value is present in this Optional, returns the value, otherwise throws NoSuchElementException.

Этот метод возвращает значение из опционала. Нужно аккуратно использовать этот метод, так как он может бросить исключение NoSuchElementException, если значение в опционале равно null. Чтобы безопасно использовать этот метод перед его вызовом надо вызвать метод isPresent()  - о нем рассказано дальше.

Пример:

public Result clientAction() {
Optional<Client> clientOpt = clientRepository.getClient("proweber1");

if (clientOpt.isPresent()) {
return clientJson(client.get());
}

return emptyResult();
}

Это не хороший код, но для примера сойдет :). Обратите внимание что мы используем метод isPresent(). Если бы мы его не использовали, то могли бы переодически ловит исключение NoSuchElementException.

ifPresent(Consumer<? super T> consumer)

If a value is present, invoke the specified consumer with the value, otherwise do nothing.

Этот метод исполняет Consumer функциональный интерфейс если значение в опционале присутствует. Давайте отрефакторим наш экшен из предыдущего примера.

public Result clientAction() {
Optional<Client> clientOpt = clientRepository.getClient("proweber1");

clientOpt.ifPresent(ClientController::clientJson);

return emptyResult();
}

Здесь мы говорим классу опционала о том, что если у тебя есть клиент, а не null, то выполни метод clientJson который совместим с интерфейсом Consumer. Здесь используется ссылка на метод: "ClientController::clientJson", об этом можно прочитать в официальной документации по Java, либо подождать статью от меня :).

isPresent()

Return true if there is a value present, otherwise false.

Возвращает булево значение true если в опционале значение есть и возвращает false если в опционале значение null. См.пример выше.

orElse(T other)

Return the value if present, otherwise return other.

Этот метод используется для того, чтобы установить значение по-умолчанию в том случае если значение в опционале отсутствует. Например.

public Result clientAction() {
Client client = clientRepository
.getClient("proweber1")
.orElse(clientNullObject());

return clientJson(client);
}

Здесь мы возвращаем пустого клиента если в опционале значение null. Это не очень разумное решение, более разумное я покажу дальше.

orElseThrow(Supplier<? extends X> exceptionSupplier)

Return the contained value, if present, otherwise throw an exception to be created by the provided supplier.

Этот метод используется для того, чтобы бросить исключение которое передается в метод orElseThrow если значения в опционале не установлено. Снова отрефакторим наш экшен :)

public Result clientAction() {
Client client = clientRepository
.getClient("proweber1")
.orElseThrow(NotFoundHttpException::new);

return clientJson(client);
}

Вот теперь все красиво. Если клиента нет, мы выкидываем исключение NotFoundHttpException, если клиент есть, возвращаем клиента и рендерим его в ответе.

orElseGet(Supplier<? extends T> other)

Return the value if present, otherwise invoke other and return the result of that invocation.

Этот метод полный аналог метода orElse, только принимает в себя функциональный интерфейс который должен вернуть значение. Пример

public Result clientAction() {
Client client = clientRepository
.getClient("proweber1")
.orElseGet(() -> clientNullObject());

return clientJson(client);
}

Или так

public Result clientAction() {
Client client = clientRepository
.getClient("proweber1")
.orElseGet(ClientController::clientNullObject);

return clientJson(client);
}

Так же опционал унаследует и переопределяет методы класса Object. Такие как

  1. hashCode
  2. toString
  3. equals

На этом все, спасибо ребят :)

Java
30.10.2016
1 ответ
авторизуйтесь чтобы ответить