Zastosowania dla opcjonalnych

Używam Javy 8 już od ponad 6 miesięcy, więc jestem całkiem zadowolony z nowych zmian w API. Nadal nie jestem pewien, kiedy używać Optional. Wydaje mi się, że waham się między chęcią użycia go wszędzie, gdzie coś może być null, a nigdzie w ogóle.

Wydaje się, że jest wiele sytuacji, w których mógłbym go użyć i nigdy nie jestem pewien, czy dodaje korzyści (czytelność / zerowe bezpieczeństwo), czy po prostu powoduje dodatkowe koszty.

Więc mam kilka przykładów i byłbym zainteresowany rozważania społeczności na temat tego, czy[4]} jest korzystne.

1 - jako publiczny Typ zwracania metody, gdy metoda może zwrócić null:

public Optional<Foo> findFoo(String id);

2-jako parametr metody, gdy param może być null:

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

3-jako opcjonalny członek fasoli:

public class Book {

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

}

4 - W Collections:

Ogólnie nie myślę:

List<Optional<Foo>>

Dodaje cokolwiek-zwłaszcza, że można używać {[10] } do usuwania null wartości itp., ale czy są jakieś dobre zastosowania dla Optional w Kolekcje?

Jakieś sprawy, które przegapiłem?

Author: John Kugelman, 2014-05-04

12 answers

Głównym punktem Optional jest zapewnienie środka dla funkcji zwracającej wartość, aby wskazać brak zwracanej wartości. Zobacz tę dyskusję . Pozwala to rozmówcy na kontynuowanie łańcucha płynnych wywołań metod.

To najbardziej pasuje do przypadku użycia #1 w pytaniu OP. Chociaż brak wartości jest bardziej precyzyjnym sformułowaniem niż null , ponieważ coś w rodzaju IntStream.findFirst nigdy nie może zwrócić null.

W przypadku użycia #2, przekazując opcjonalny argument do metody, może to zadziałać, ale jest to raczej niezdarne. Załóżmy, że masz metodę, która pobiera ciąg znaków, po którym następuje opcjonalny drugi ciąg znaków. Zaakceptowanie Optional jako drugiego arg skutkowałoby kodem w następujący sposób:

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

Nawet zaakceptowanie null jest ładniejsze:

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

Prawdopodobnie najlepiej jest mieć przeciążoną metodę, która akceptuje pojedynczy argument Łańcuchowy i podaje wartość domyślną dla drugiego:

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

To ma ograniczenia, ale jest o wiele ładniejszy niż którykolwiek z powyższych.

Przypadki użycia #3 oraz #4, posiadanie Optional w polu klasy lub w strukturze danych jest uważane za niewłaściwe korzystanie z API. Po pierwsze, jest to sprzeczne z głównym celem projektu Optional, Jak podano na górze. Po drugie, to nie dodaje żadnej wartości.

Istnieją trzy sposoby radzenia sobie z brakiem wartości w Optional: dostarczenie wartości zastępczej, wywołanie funkcji w celu dostarczenia wartości zastępczej lub rzucenie wyjątek. Jeśli przechowujesz w polu, zrobisz to w czasie inicjalizacji lub przypisania. Jeśli dodajesz wartości do listy, jak wspomniano w OP, masz dodatkowy wybór: po prostu nie dodajesz wartości, a tym samym "spłaszczasz" nieobecne wartości.

Jestem pewien, że ktoś mógłby wymyślić jakieś wymyślone przypadki, w których naprawdę chce przechowywać Optional w polu lub kolekcji, ale ogólnie najlepiej tego unikać.

 144
Author: Stuart Marks,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-05 04:26:51

Jestem spóźniony na mecz, ale jeśli to coś warte, chcę dodać moje 2 centy. Idą wbrew celowi projektu Optional, co jest dobrze podsumowane przez odpowiedź Stuarta Marksa , ale nadal jestem przekonany o ich ważności (oczywiście).

Użyj Opcji Wszędzie

Ogólnie

Napisałem cały wpis na blogu o używaniu Optional ale to w zasadzie sprowadza się do tego:

  • Zaprojektuj swoje klasy, aby uniknąć opcjonalności wszędzie tam, gdzie możliwe do wykonania
  • we wszystkich pozostałych przypadkach domyślnym powinno być użycie Optional zamiast null
  • ewentualnie zrobić wyjątki dla:
    • zmienne lokalne
    • zwraca wartości i argumenty do metod prywatnych
    • W 2007 roku firma została założona przez firmę Microsoft.]}

Dwa pierwsze wyjątki mogą zmniejszyć postrzegane narzuty nawijania i rozpakowywania odwołań w Optional. Są tak dobrane, że null nigdy nie może legalnie przejść granicę z jednej instancji do drugiej.

Zauważ, że to prawie nigdy nie pozwoli Optional S w kolekcjach, które są prawie tak złe jak null s. po prostu tego nie rób. ;)

Odnośnie Twoich pytań

    Tak.
  1. jeśli przeciążenie nie jest opcją, tak.
  2. jeśli inne podejścia (podklasowanie, dekorowanie, ...) nie ma opcji, Tak.
  3. Proszę, nie!

Zalety

Zmniejsza to obecność nulls w Twoim kodzie bazy, choć nie eliminuje ich. Ale to nie jest nawet główny punkt. Istnieją inne ważne zalety:

Wyjaśnia Intencję

Użycie Optional jasno wyraża, że zmienna jest, cóż, opcjonalna. Każdy czytelnik Twojego kodu lub konsument twojego API zostanie pobity przez fakt, że może tam nic nie być i że sprawdzenie jest konieczne przed uzyskaniem dostępu do wartości.

Usuwa Niepewność

Bez Optional znaczenia null występowanie jest niejasne. Może to być prawna reprezentacja Państwa (zob. Map.get) lub błąd implementacji, taki jak brakująca lub nieudana inicjalizacja.

To zmienia się dramatycznie wraz z uporczywym używaniem Optional. Tutaj już Występowanie null oznacza obecność błędu. (Ponieważ gdyby wartość mogła zostać pominięta, użyto by Optional.) To sprawia, że debugowanie wyjątku wskaźnika null jest znacznie łatwiejsze, ponieważ pytanie o znaczenie tego null jest już odebrałem.

Więcej Kontroli Null

Teraz, gdy już nic nie może być null, to może być egzekwowane wszędzie. Niezależnie od tego, czy z adnotacjami, twierdzeniami czy zwykłymi sprawdzeniami, nigdy nie musisz myśleć o tym, czy ten argument lub ten typ zwracanego typu może być null. Nie może!

Wady

Oczywiście, nie ma srebrnej kuli...

Wydajność

Owijanie wartości (zwłaszcza pierwotnych) w dodatkową instancję może pogorszyć wydajność. W ciasnym pętle mogą stać się zauważalne lub nawet gorsze.

Należy zauważyć, że kompilator może być w stanie obejść dodatkowe odniesienie dla krótkotrwałych okresów życia Optionals. W Java 10 typy wartości mogą dodatkowo zmniejszyć lub usunąć karę.

Serializacja

Optional nie jest serializowalny, ale obejście pracy nie jest zbyt skomplikowane.

Niezmienność

Ze względu na niezmienność typów generycznych w Javie, niektóre operacje stają się uciążliwe, gdy rzeczywisty typ wartości jest wepchnięty do argumentu typu ogólnego. Przykład podano tutaj (patrz "polimorfizm parametryczny").

 58
Author: Nicolai,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:33:26

Osobiście wolę używać IntelliJ ' S Code Inspection Tool do używania kontroli @NotNull i @Nullable, ponieważ są to w dużej mierze kontrole czasu kompilacji (mogą mieć pewne kontrole runtime) ma to niższy narzut pod względem czytelności kodu i wydajności runtime. Nie jest tak rygorystyczne jak stosowanie opcji, jednak ten brak rygoru powinien być poparty przyzwoitymi testami jednostkowymi.

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 = ..

To działa z Java 5 i nie ma potrzeby zawijania i rozpakowywania wartości. (lub tworzenie obiektów wrappera)

 20
Author: Peter Lawrey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-04 12:18:41

Myślę, że Guava Optional i ich strona wiki całkiem dobrze to ujęła:

Poza wzrostem czytelności wynikającym z nadania null nazwy, największą zaletą opcjonalnego jest jego idiot-proof-ness. Zmusza cię to do aktywnego myślenia o nieobecnym przypadku, jeśli chcesz, aby twój program w ogóle się skompilował, ponieważ musisz aktywnie rozpakować opcjonalny i rozwiązać ten przypadek. Null sprawia, że niepokojąco łatwo jest po prostu zapomnieć o rzeczach i chociaż FindBugs pomaga, nie myślimy o tym rozwiązuje ten problem prawie tak samo.

Jest to szczególnie istotne, gdy zwracasz wartości, które mogą, ale nie muszą być "obecne."Ty (i inni) o wiele bardziej prawdopodobne jest, że zapomnisz o tym drugim.metoda (A, b) może zwrócić wartość null, niż prawdopodobnie zapomnisz, że a może być null, gdy implementujesz inne.metoda. Zwracanie opcji uniemożliwia wywołującym zapomnienie o tej sprawie, ponieważ muszą sami rozpakować obiekt, aby skompilować swój kod. -- (Źródło: Guava Wiki-używanie i unikanie null-jaki to ma sens?)

Optional dodaje trochę kosztów, ale myślę, że jego wyraźną zaletą jest to, że explicit że obiekt może być nieobecny i wymusza to, że programiści zajmują się tą sytuacją. Zapobiega to zapomnieniu o czeku ukochanej.

Na przykładzie 2, myślę, że jest to o wiele bardziej wyraźny kod do napisania:

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

Niż

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

Dla mnie, Optional lepiej rejestruje fakt, że nie ma karty dźwiękowej.

Moje 2 o twoich punktach:

  1. - Nie jestem tego pewien. Może zwrócę Result<Foo>, który może być pusty lub zawierać Foo. Jest to podobne pojęcie, ale tak naprawdę nie Optional.
  2. public Foo doSomething(String id, Optional<Bar> barOptional); - wolałbym @Nullable i sprawdzić findbugs, jak w odpowiedź Petera Lawreya - Zobacz także tę dyskusję.
  3. Twój przykład książki - nie jestem pewien, czy użyłbym Opcjonalne wewnętrznie, które mogą zależeć od złożoności. W przypadku "API" książki użyłbym Optional<Index> getIndex(), aby wyraźnie wskazać, że książka może nie mieć indeksu.
  4. Nie używałbym go w kolekcjach, raczej nie pozwalałbym na wartości null w kolekcjach

Ogólnie rzecz biorąc, starałbym się zminimalizować przechodzenie nulls. (raz spalony...) Myślę, że warto znaleźć odpowiednie abstrakcje i wskazać kolegom programistom jaką określoną wartość zwracaną rzeczywiście reprezentuje.

 18
Author: Behe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:50

From Oracle tutorial :

Celem opcjonalnej nie jest zastąpienie każdego pojedynczego odniesienia null w bazie kodu, ale raczej pomoc w projektowaniu lepszych interfejsów API, w których-po prostu czytając podpis metody-użytkownicy mogą powiedzieć, czy oczekują opcjonalnej wartości. Dodatkowo opcja wymusza aktywne rozpakowanie opcji, aby poradzić sobie z brakiem wartości; w rezultacie chronisz swój kod przed niezamierzonymi wyjątkami ze wskaźnikiem null.

 8
Author: ctomek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-31 07:53:28

Oto ciekawe zastosowanie (jak sądzę) dla... Testy.

Zamierzam mocno przetestować jeden z moich projektów i dlatego buduję twierdzenia; tylko są rzeczy, które muszę zweryfikować, A innych nie.

Dlatego buduję rzeczy do twierdzenia i używam twierdzenia do ich weryfikacji, jak to:

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);
        }
    }
}

W klasie NodeAssert robię to:

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's label is null! I didn't expect it to be"
        ).isNotNull();
        assertThat(thisLabel).overridingErrorMessage(
            "node's 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;
    }
}

Co oznacza, że assert uruchamia się tylko wtedy, gdy chcę sprawdzić etykietę!

 2
Author: fge,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-05 05:53:47
 2
Author: gontard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-26 15:52:17

Wydaje się Optional jest użyteczne tylko wtedy, gdy Typ T w opcjonalnym jest typem prymitywnym jak int, long, char, itd. Dla" prawdziwych " klas, to nie ma sensu dla mnie, ponieważ można użyć null wartość i tak.

Myślę, że został wzięty stąd (lub z innego podobnego pojęcia językowego).

Nullable<T>

W C# to {[5] } zostało wprowadzone dawno temu do zawijania typów wartości.

 1
Author: peter.petrov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-04 10:19:34

Nie sądzę, aby opcjonalne było ogólnym substytutem metod, które potencjalnie zwracają wartości null.

Podstawowa idea brzmi: brak wartości nie oznacza, że potencjalnie jest dostępna w przyszłości. Jest to różnica między findById(-1) a findById (67).

Główną informacją dla rozmówcy jest to, że może on nie liczyć na podaną wartość, ale może być dostępna w pewnym momencie. Może znowu zniknie i wróci później jeszcze raz. To jak włącznik/wyłącznik. Masz "opcję", aby włączyć lub wyłączyć światło. Ale nie masz opcji, jeśli nie masz światła do włączenia.

Więc uważam, że zbyt niechlujne jest wprowadzanie opcji wszędzie tam, gdzie potencjalnie zwrócono wcześniej null. Nadal będę używać null, ale tylko w ograniczonych obszarach, takich jak korzeń drzewa, leniwa inicjalizacja i jawne find-methods.

 1
Author: oopexpert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-31 20:30:42

1 - jako publiczny Typ zwracania metody, gdy metoda może zwrócić null:

Otodobry artykuł , który pokazuje przydatność usecase #1. Tam ten kod

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

Przekształca się w to

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

Używając opcjonalnej wartości zwracanej przez odpowiednie metody getter.

 1
Author: artifex,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-03 11:37:05

Java SE 8 wprowadza nową klasę o nazwie java.util.Opcjonalne,

Możesz utworzyć puste opcjonalne lub opcjonalne z wartością null.

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

I tutaj jest Opcjonalne z wartością inną niż null:

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

Zrób coś, jeśli wartość jest obecna

Teraz, gdy masz opcjonalny obiekt, możesz uzyskać dostęp do dostępnych metod, aby jawnie radzić sobie z obecnością lub brakiem wartości. Zamiast pamiętać o sprawdzeniu null, w następujący sposób:

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

Ty można użyć metody ifPresent () w następujący sposób:

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());

        }

    }
}
 0
Author: Kumar Abhishek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-17 03:55:12

If you don ' t like to check this way

if(obj!=null){

}

Wtedy możesz to zrobić w ten sposób, to wszystko co widzę

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

}

Zasadniczo, ma to dać złudzenie, że lepiej radzisz sobie z wartościami null.

 -1
Author: Vikash,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-27 12:27:03