Использование для необязательного

Используя Java 8 сейчас для 6+ месяцев или около того, я очень доволен новыми изменениями API. Одна область, в которой я до сих пор не уверен, - это когда использовать Optional. Кажется, я качаюсь между желанием использовать его везде, где что-то может быть null, и нигде вообще.

Кажется, что есть много ситуаций, когда я мог бы использовать его, и я никогда не уверен, добавляет ли он преимущества (читаемость/нулевая безопасность) или просто вызывает дополнительные накладные расходы.

Итак, у меня есть несколько примеров, и меня бы интересовали мысли сообщества о том, является ли Optional полезным.

1 - как открытый тип возвращаемого метода, когда метод может вернуть значение null:

public Optional<Foo> findFoo(String id);

2 - как параметр метода, когда параметр может быть null:

public Foo doSomething(String id, Optional<Bar> barOptional);

3 - как необязательный член компонента:

public class Book {

  private List<Pages> pages;
  private Optional<Index> index;

}

4 - В Collections:

В общем, я не думаю:

List<Optional<Foo>>

добавляет что-либо - тем более, что можно использовать filter() для удаления null значений и т.д., но есть ли какие-либо хорошие применения для Optional в коллекциях?

Любые случаи, которые я пропустил?

Ответ 1

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

Это наиболее близко соответствует случаю использования # 1 в вопросе OP. Хотя, отсутствие значения является более точной формулировкой, чем null, поскольку нечто вроде IntStream.findFirst никогда не может вернуть значение null.

В случае использования # 2, передавая необязательный аргумент методу, это может быть сделано для работы, но оно довольно неуклюжие. Предположим, что у вас есть метод, который берет строку, за которой следует необязательная вторая строка. Принятие Optional в качестве второго аргумента приведет к следующему коду:

foo("bar", Optional.of("baz"));
foo("bar", Optional.empty());

Даже прием нулевого значения более приятный:

foo("bar", "baz");
foo("bar", null);

Наверное, лучше всего иметь перегруженный метод, который принимает один строковый аргумент и предоставляет значение по умолчанию для второго:

foo("bar", "baz");
foo("bar");

У этого есть ограничения, но это намного лучше, чем любой из вышеперечисленных.

Использовать случаи # 3 и # 4, имея Optional в поле класса или в структуре данных, считается неправильным использованием API. Во-первых, это противоречит основной цели дизайна Optional, как указано выше. Во-вторых, он не добавляет никакого значения.

Существует три способа борьбы с отсутствием значения в Optional: для предоставления заменяющего значения, для вызова функции для предоставления заменяющего значения или для исключения исключения. Если вы сохраняете поле, вы должны сделать это при инициализации или времени назначения. Если вы добавляете значения в список, как указано в OP, у вас есть дополнительный выбор, просто не добавляя значение, тем самым "сглаживая" отсутствующие значения.

Я уверен, что кто-то может придумать некоторые надуманные случаи, когда они действительно хотят хранить Optional в поле или коллекции, но в целом лучше избегать этого.

Ответ 2

Я опаздываю на игру, но для чего это стоит, я хочу добавить свои 2 Cents. Они идут против цели дизайна Optional, который хорошо обобщен Ответ Стюарта Маркса, но я все еще убежден в их достоверности (очевидно).

Использовать опцию "Везде"

В целом

Я написал целый блог об использовании Optional, но в основном это сводится к следующему:

  • проектируйте свои классы, чтобы избежать возможности выбора, где это возможно.
  • во всех остальных случаях по умолчанию следует использовать Optional вместо null
  • возможно сделать исключения для:
    • локальные переменные
    • возвращает значения и аргументы в частные методы
    • критические блоки кода производительности (без догадок, использование профилировщика)

Первые два исключения могут уменьшить воспринимаемые служебные данные для обертывания и разворачивания ссылок в Optional. Они выбираются так, что нуль никогда не может законно передавать границу из одного экземпляра в другой.

Обратите внимание, что это почти никогда не позволит Optional в коллекциях, что почти так же плохо, как null s. Только не делай этого.;)

Относительно ваших вопросов

  • Да.
  • Если перегрузка не является параметром, да.
  • Если другие подходы (подклассы, декорирование,...) не являются параметрами, да.
  • Пожалуйста, нет!

Преимущества

Это уменьшает присутствие null в базе кода, хотя оно не уничтожает их. Но это даже не главное. Существуют и другие важные преимущества:

Уточняет намерение

Использование Optional ясно выражает, что переменная, ну, необязательна. Любой читатель вашего кода или потребителя вашего API будет избит по голове тем, что там не может быть ничего, и перед доступом к значению необходимо проверить.

Устраняет неопределенность

Без Optional смысл события null неясен. Это может быть юридическое представление состояния (см. Map.get) или ошибка реализации, например, отсутствие или неудачная инициализация.

Это резко меняется при постоянном использовании Optional. Здесь уже появление null означает наличие ошибки. (Поскольку, если значение было разрешено отсутствовать, был бы использован Optional.) Это значительно облегчает отладку исключения нулевого указателя, поскольку вопрос о значении этого null уже ответил.

Подробнее Null Checks

Теперь, когда ничего не может быть null, это может быть применено повсюду. Независимо от того, с аннотациями, утверждениями или обычными проверками, вам никогда не придется думать о том, может ли этот аргумент или этот тип возврата быть нулевым. Он не может!

Недостатки

Конечно, нет серебряной пули...

Производительность

Значения упаковки (особенно примитивы) в дополнительный экземпляр могут ухудшить производительность. В плотных петлях это может стать заметным или даже хуже.

Обратите внимание, что компилятор может обходить дополнительную ссылку для коротких жизненных циклов Optional s. В Java 10 типы значений могут дополнительно уменьшить или удалить штраф.

Сериализация

Optional не является сериализуемым, но обходное решение не слишком сложно.

инвариантность

Из-за инвариантности родовых типов в Java некоторые операции становятся громоздкими, когда тип фактического значения вводится в аргумент общего типа. Пример приведен ниже (см. "Параметрический полиморфизм" ).

Ответ 3

Лично я предпочитаю использовать IntelliJ Code Inspection Tool для использования @NotNull и @Nullable как это в основном время компиляции (может иметь некоторые проверки во время выполнения). Это снижает издержки с точки зрения читабельности кода и производительности во время выполнения. Это не так строго, как использование Optional, однако это отсутствие строгости должно быть подкреплено достойными юнит-тестами.

public @Nullable Foo findFoo(@NotNull String id);

public @NotNull Foo doSomething(@NotNull String id, @Nullable Bar barOptional);

public class Book {

  private List<Pages> pages;
  private @Nullable Index index;

}

List<@Nullable Foo> list = ..

Это работает с Java 5 и не нужно оборачивать и разворачивать значения. (или создайте объекты-оболочки)

Ответ 4

Я думаю, что Гуава Факультативно и их вики-страница довольно хорошо это описывают:

Помимо повышения читабельности за счет присвоения нулевому имени, самым большим преимуществом Optional является его защищенность от идиотов. Это заставляет вас активно думать об отсутствующем кейсе, если вы хотите, чтобы ваша программа вообще компилировалась, поскольку вы должны активно развернуть Optional и заняться этим кейсом. Null позволяет легко забыть о вещах, и хотя FindBugs помогает, мы не думаем, что это решает проблему почти так же.

Это особенно актуально, когда вы возвращаете значения, которые могут присутствовать или не присутствовать. Вы (и другие) с гораздо большей вероятностью забудете, что other.method(a, b) может вернуть нулевое значение, чем вы, вероятно, забудете, что a может быть нулевым, когда вы реализуете other.method. Возврат Optional делает невозможным для вызывающих абонентов забыть об этом случае, так как они должны сами развернуть объект для компиляции своего кода. - (Источник: Guava Wiki - Использование и избежание нуля - Какой смысл?)

Optional добавляет некоторые накладные расходы, но я думаю, что его явное преимущество заключается в том, чтобы сделать явным, что объект может отсутствовать, и это заставляет программистов справляться с ситуацией. Это предотвращает, что кто - то забывает любимый != null чека.

Взяв пример 2, я думаю, что это гораздо более явный код для записи:

if(soundcard.isPresent()){
  System.out.println(soundcard.get());
}

чем

if(soundcard != null){
  System.out.println(soundcard);
}

Для меня Optional лучше фиксирует тот факт, что звуковой карты нет.

Мои 2 ¢ о ваших баллах:

  1. public Optional<Foo> findFoo(String id); - Я не уверен в этом. Может быть, я бы вернул Result<Foo> который может быть пустым или содержать Foo. Это похожая концепция, но на самом деле не является Optional.
  2. public Foo doSomething(String id, Optional<Bar> barOptional); - Я бы предпочел @Nullable и проверку findbugs, как в ответе Питера Лоури - см. Также это обсуждение.
  3. Пример вашей книги - я не уверен, что буду использовать Optional для внутреннего использования, это может зависеть от сложности. Для "API" книги я бы использовал Optional<Index> getIndex() чтобы явно указать, что книга может не иметь индекса.
  4. Я бы не использовал его в коллекциях, скорее не допустив нулевые значения в коллекциях

В общем, я бы постарался свести к минимуму передачу вокруг null s. (После того, как сгорел...) Я думаю, что стоит найти соответствующие абстракции и указать коллегам-программистам, что на самом деле представляет определенное возвращаемое значение.

Ответ 5

Из Учебник Oracle:

Цель Необязательного - не заменять каждую единственную нулевую ссылку в вашей кодовой базе, а скорее помогать разрабатывать лучшие API-интерфейсы, в которых - просто читая подписи метода - пользователи могут определить, следует ли ожидать дополнительного значения. Кроме того, Необязательно заставляет вас активно разворачивать Необязательный, чтобы иметь дело с отсутствием ценности; в результате вы защищаете свой код от непреднамеренных исключений нулевого указателя.

Ответ 6

1 - как открытый тип возвращаемого метода, когда метод может возвратить нуль:

Вот хорошая статья, которая показывает полезность варианта использования № 1. Там этот код

...
if (user != null) {
    Address address = user.getAddress();
    if (address != null) {
        Country country = address.getCountry();
        if (country != null) {
            String isocode = country.getIsocode();
            isocode = isocode.toUpperCase();
        }
    }
}
...

превращается в это

String result = Optional.ofNullable(user)
  .flatMap(User::getAddress)
  .flatMap(Address::getCountry)
  .map(Country::getIsocode)
  .orElse("default");

используя Optional в качестве возвращаемого значения соответствующих методов получения.

Ответ 7

Есть уже несколько ресурсов об этой функции:

Ответ 8

Вот интересное использование (я считаю) для... Тесты.

Я намерен серьезно протестировать один из моих проектов, и поэтому я строю утверждения; только есть вещи, которые я должен проверить, а другие - нет.

Поэтому я строю вещи, чтобы утверждать и использовать утверждение для их проверки, например:

public final class NodeDescriptor<V>
{
    private final Optional<String> label;
    private final List<NodeDescriptor<V>> children;

    private NodeDescriptor(final Builder<V> builder)
    {
        label = Optional.fromNullable(builder.label);
        final ImmutableList.Builder<NodeDescriptor<V>> listBuilder
            = ImmutableList.builder();
        for (final Builder<V> element: builder.children)
            listBuilder.add(element.build());
        children = listBuilder.build();
    }

    public static <E> Builder<E> newBuilder()
    {
        return new Builder<E>();
    }

    public void verify(@Nonnull final Node<V> node)
    {
        final NodeAssert<V> nodeAssert = new NodeAssert<V>(node);
        nodeAssert.hasLabel(label);
    }

    public static final class Builder<V>
    {
        private String label;
        private final List<Builder<V>> children = Lists.newArrayList();

        private Builder()
        {
        }

        public Builder<V> withLabel(@Nonnull final String label)
        {
            this.label = Preconditions.checkNotNull(label);
            return this;
        }

        public Builder<V> withChildNode(@Nonnull final Builder<V> child)
        {
            Preconditions.checkNotNull(child);
            children.add(child);
            return this;
        }

        public NodeDescriptor<V> build()
        {
            return new NodeDescriptor<V>(this);
        }
    }
}

В классе NodeAssert я делаю это:

public final class NodeAssert<V>
    extends AbstractAssert<NodeAssert<V>, Node<V>>
{
    NodeAssert(final Node<V> actual)
    {
        super(Preconditions.checkNotNull(actual), NodeAssert.class);
    }

    private NodeAssert<V> hasLabel(final String label)
    {
        final String thisLabel = actual.getLabel();
        assertThat(thisLabel).overridingErrorMessage(
            "node label is null! I didn't expect it to be"
        ).isNotNull();
        assertThat(thisLabel).overridingErrorMessage(
            "node label is not what was expected!\n"
            + "Expected: '%s'\nActual  : '%s'\n", label, thisLabel
        ).isEqualTo(label);
        return this;
    }

    NodeAssert<V> hasLabel(@Nonnull final Optional<String> label)
    {
        return label.isPresent() ? hasLabel(label.get()) : this;
    }
}

Это означает, что assert действительно только срабатывает, если я хочу проверить метку!

Ответ 9

Я не думаю, что Необязательный - это общий заменитель методов, которые потенциально возвращают нулевые значения.

Основная идея: отсутствие значения не означает, что оно потенциально доступно в будущем. Это разница между findById (-1) и findById (67).

Основная информация Опций для вызывающего абонента заключается в том, что он не может рассчитывать на указанное значение, но может быть доступно в течение некоторого времени. Возможно, он снова исчезнет и вернется позже еще раз. Это как переключатель включения/выключения. У вас есть "опция" для включения или выключения подсветки. Но у вас нет выбора, если у вас нет света для включения.

Итак, я нахожу это слишком беспорядочным, чтобы вводить Опционы везде, где ранее был возвращен null. Я по-прежнему буду использовать null, но только в ограниченных областях, таких как корень дерева, ленивая инициализация и явные методы поиска.

Ответ 10

Optional класс позволяет вам избежать использования null и предоставить лучшую альтернативу:

  • Это побуждает разработчика проверять наличие, чтобы избежать неперехваченных исключений NullPointerException.

  • API становится лучше документированным, потому что можно увидеть, где ожидать значений, которые могут отсутствовать.

Optional предоставляет удобный API для дальнейшей работы с объектом: isPresent(); get(); orElse(); orElseGet(); orElseThrow(); map(); filter(); flatmap().

Кроме того, многие фреймворки активно используют этот тип данных и возвращают его из своего API.

Ответ 11

Кажется, что Optional полезен, если тип T в необязательном - это примитивный тип типа int, long, char и т.д. Для "реальных" классов это не имеет смысла для меня, поскольку вы может использовать значение null в любом случае.

Я думаю, что это было взято отсюда (или из другой аналогичной концепции языка).

Nullable<T>

В С# этот Nullable<T> был введен давно, чтобы обернуть типы значений.

Ответ 12

Если вам не нравится проверять этот путь

if(obj!=null){

}

то вы можете сделать это так, что все, что я вижу

if(Optional.ofNullable(obj).isPresent()){

}

В принципе, это дает иллюзию, что вы более эффективно обрабатываете ненулевые значения.

Ответ 13

Optional семантика аналогична неизменяемому экземпляру шаблона проектирования Iterator:

  • это может или не может ссылаться на объект (как дано isPresent())
  • он может быть разыменован (используя get()), если он ссылается на объект
  • но его нельзя продвинуть на следующую позицию в последовательности (у него нет метода next()).

Поэтому рассмотрите возможность возврата или передачи Optional в тех контекстах, где вы, возможно, ранее рассматривали использование Iterator Java.

Ответ 14

В Java просто не используйте их, если вы не зависимы от функционального программирования.

Им не место в качестве аргументов метода (я обещаю, что однажды кто-нибудь передаст вам необязательный параметр null, а не просто необязательный параметр, который пуст).

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

FP и цепочки мало места в императивном языке, таком как java, потому что его очень трудно отлаживать, а не просто читать. Когда вы выходите на линию, вы не можете знать ни состояние, ни цель программы; вам нужно разобраться с этим (в коде, который часто не ваш и имеет много кадровых стеков, несмотря на пошаговые фильтры), и вам нужно добавить множество точек останова, чтобы убедиться, что он может остановиться в добавленном вами коде/лямбде, вместо того, чтобы просто ходить по тривиальным линиям if/else/.

Если вам нужно функциональное программирование, выберите что-то еще, кроме java, и надеюсь, что у вас есть инструменты для его отладки.

Ответ 15

Java SE 8 представляет новый класс с именем java.util.Optional,

Вы можете создать пустой Optional или Optional с нулевым значением.

Optional<String> emptyOptional = Optional.empty(); 

А вот Optional с ненулевым значением:

String valueString = new String("TEST");
Optional<String> optinalValueString = Optional.of(valueString );

Сделайте что-нибудь, если значение присутствует

Теперь, когда у вас есть необязательный объект, вы можете получить доступ к доступным методам, чтобы явно иметь дело с наличием или отсутствием значений. Вместо того, чтобы не забыть сделать проверку на ноль, следующим образом:

String nullString = null;
if (nullString != null) {
    System.out.println(nullString);
}

Вы можете использовать метод ifPresent() следующим образом:

Optional<String> optinalString= null;
optinalString.ifPresent(System.out::println);

package optinalTest;

import java.util.Optional;

public class OptionalTest {
    public Optional<String> getOptionalNullString() {
        return null;
//      return Optional.of("TESt");
    }

    public static void main(String[] args) {

        OptionalTest optionalTest = new OptionalTest();

        Optional<Optional<String>> optionalNullString = Optional.ofNullable(optionalTest.getOptionalNullString());

        if (optionalNullString.isPresent()) {
            System.out.println(optionalNullString.get());
        }
    }
}