пятница, 7 апреля 2017 г.

Функциональное программирование в Java 8 (Часть 2): Optionals

Всем привет.

После того, как  в прошлой статье мы сделали наш первый большой шаг в ФП, в сегоднешней части поговорим об Optionals.

Зачем нужен Optional?

Если быть откровенным, вы так же думаете, что null раздражает? Для каждого объекта, если его значение может быть null, нужно проверять, null он или нет:

if(argument == null) {
    throw new NullPointerException();
}

Стремное решение. Этот шаблон раздумает код, так же про него можно легко забыть. Как это исправить?

Знакомство с Optionals

В Java 8, был представлен java.util.Optional<T>, чтобы обрабатыать объекты, которые не инициализированы. Это объект-контейнер, который может хранить другой объект. Дженерик T - тип объекта, который вы хотите хранить. 

Integer i = 5;
Optional<Integer> optinalI = Optional.of(i);

У класса Optional нет никаких публичных конструкторов. Чтобы создать optional, необходимо использовать Optional.of(object) или Optional.ofNullable(object).  Первый сособ используется, если объект никогда не будет null. Второй - если объект может быть null.

Как работает Optional

У Optional 2 сосотояния. Либо в нем хранится null, либо объект. Если хранится объект - optional называют существующим, если null - пустым.  Чтобы получить существующий объект можно использовать Optional.get(),  но нужно быть аккуратным, если вызвать к пустому объекту - будет исключение: NoSuchElementException. Чтобы проверить что объект существует нужно вызвать Optional.isPresent().

Примеры:

public void playingWithOptionals() {
    String s = "Hello World!";

    String nullString = null;

    Optional<String> optionalS1 = Optional.of(s); // Will work

    Optional<String> optionalS2 = Optional.ofNullable(s); // Will work too

    Optional<String> optionalNull1 = Optional.of(nullString); // -> NullPointerException

    Optional<String> optionalNull2 = Optional.ofNullable(nullString); // Will work

    System.out.println(optionalS1.get()); // prints "Hello World!"

    System.out.println(optionalNull2.get()); // -> NoSuchElementException

    if(!optionalNull2.isPresent()) {

        System.out.println("Is empty"); // Will be printed

    }

}

Частые проблемы при использовании Optional

1. Работа с Optional и null
public void workWithFirstStringInDB() {

    DBConnection dB = new DBConnection();

    Optional<String> first = dB.getFirstString();

    if(first != null) {

        String value = first.get(); 

        //... 

    }

}
Так использовать Optional нельзя! Когда вы получаете Optional (В примере из DB), не нужно проверять значение объекта на null. Если в DB нет строки, она вернет Optional.empty(), а не null. Если получите пустой элемент, как в примере, всеравно будет исключение NoSuchElementException.
2. Использование isPresented() и get().
2.1 Использование значение, когда объект представлен.
public void workWithFirstStringInDB() {

    DBConnection dB = new DBConnection();

    Optional<String> first = dB.getFirstString();

    if(first.isPresent()) {

        String value = first.get(); 

        //... 

    }

}
Как было уже сказано, необходимо быть на 100% уверенным, прежде, чем использовать Optional.get(). Вы не получите NoSuchElementException в обновленной функции. Но не нужно проверять isPresent() + get() вместе. Т.к. не нужно забывать про null,  сначала, нужно проверить first != null. И мы получаем тот же результат! И как же избавиться от этого надоедливого блока? Вот как:
public void workWithFirstStringInDB() {

    DBConnection dB = new DBConnection();

    Optional<String> first = dB.getFirstString();

    first.ifPresent(value -> /*...*/);

}
Метод Optional.ifPresent() наш новый лучший заменитель блока проверки. Он принимает Функцию, т.е. лямду или ссылку на метод, и выполняет ее, если объект существует. 
2.2 Возвращение исмененного объекта.
public Integer doubleValueOrZero(Optional<Integer> value) {

    if(value.isPresent()) {

       return value.get() * 2;

    }

    return 0;

}
В этом методе мы хотид удвоить объект, если он представлен. Иначе вернуть 0. Данный пример работает, но это не функциональный способ решения проблемы. У нас есть 2 функции, чтобы сделать этот метод красивее. Первая Optional.map(Function<T, R> mapper) и вторая Optional.orElse(T other). Мap берет функцию, применяет ее и возвращает результат опять обернутый в Optional, если объект пустой - будет возвращен пустой Optional. orElse вернет значение Optional, если оно есть или вернет взначение, котороек передано как параметр orElse(object). Учитывая это, функцию можно реализовать в одну строку.
public Integer doubleValueOrZero(Optional<Integer> value) {

    return value.map(i -> i * 2).orElse(0);

}

Когда использовать объект, который м.б. null, а когда Optional

Вы можете найти много книг, лекций и дискуссий о вопросе: использовать мне null или Optional в конкретной ситуации. Оба решения могут быть правильными. По ссылке можно найти хорошее правило, которое можно применять почти во всех случаях. Использвать Optional, когда существует явная необходимость показать, что результата нет или null может стать причиной ошибки.
Т.е. не нужно использовать Optional так:
public String defaultIfOptional(String string) {

    return Optional.ofNullable(string).orElse("default");

}
Т.к. проверка на null более читабельна:
public String defaultIfOptional(String string) {

    return (string != null) ? string : "default";

}
Вы должны использовать Optionals, как возвращаемое значение из функции. Не стоит создавать новые, чтобы создать классную цепочку методов, как в примере выше. В большинстве случаев null достаточно.

Вывод

Вот и все на сегодня! Мы разобрались с Optional. Это класс-контейнер  для других классов, который либо существует, либо пустой. Мы удалили некоторый общий кода, который поставлялся с Optionals, и  снова использовали функции как объекты. Мы также обсудили, когда следует использовать null и когда Optionals.

В следующей части мы будем использовать Streams как новый способ обработки коллекции объектов, таких как Iterables и Collections.

Спасибо за чтение и хорошего дня,

Никлас

ps это мой перевод данной статьи

Комментариев нет :

Отправить комментарий