Jaki jest sens opcjonalnej klasy guawy

89

Niedawno czytałem o tym i widziałem, jak ludzie używają tej klasy, ale w prawie wszystkich przypadkach używanie również nullby działało - jeśli nie bardziej intuicyjnie. Czy ktoś może podać konkretny przykład, w którym można Optionalby osiągnąć coś, nullczego nie można lub w znacznie czystszy sposób? Jedyne, co przychodzi mi do głowy, to używać go z kluczami Mapsnie akceptującymi null, ale nawet to można by zrobić z bocznym "mapowaniem" wartości null. Czy ktoś może podać mi bardziej przekonujący argument? Dziękuję Ci.

PROMIEŃ
źródło
12
losowo rant: Nienawidzę, kiedy ludzie nadużywać na „wzór” i sprawiają, że wygląd tak brzydki kod jakiegoś teoretycznego korzyści, które nie istnieje ...
RAY
Od Java8 nie użyłbym już tej klasy Guava, ponieważ jest mniej wydajna niż JDK. Zobacz stackoverflow.com/a/10756992/82609
Sebastien Lorber

Odpowiedzi:

155

Członek zespołu Guava tutaj.

Prawdopodobnie największą wadą programu nulljest to, że nie jest oczywiste, co to powinno oznaczać w danym kontekście: nie ma ilustracyjnej nazwy. Nie zawsze jest oczywiste, że nulloznacza to „brak wartości dla tego parametru” - do cholery, jako wartość zwracana, czasami oznacza to „błąd”, a nawet „sukces” (!!), lub po prostu „prawidłowa odpowiedź to nic”. Optionaljest często pojęciem, które faktycznie masz na myśli, ustawiając zmienną dopuszczającą wartość null, ale nie zawsze. Jeśli tak nie jest, zalecamy napisanie własnej klasy, podobnej do Optionalinnego schematu nazewnictwa, ale z innym schematem, aby wyjaśnić, co tak naprawdę masz na myśli.

Ale powiedziałbym, że największą zaletą Optionalnie jest czytelność: zaletą jest to, że jest odporny na idiotę. Zmusza cię do aktywnego myślenia o nieobecnym przypadku, jeśli chcesz, aby program w ogóle się kompilował, ponieważ musisz aktywnie rozpakować Optionali zająć się tym przypadkiem. Null sprawia, że ​​po prostu zapomnienie o rzeczach jest niepokojąco łatwe i chociaż FindBugs pomaga, nie sądzę, że rozwiązuje ten problem równie dobrze. Jest to szczególnie istotne, gdy zwracasz wartości, które mogą, ale nie muszą być „obecne”. Ty (i inni) jesteście znacznie bardziej skłonni zapomnieć, że other.method(a, b)może zwrócić nullwartość, niż prawdopodobnie zapomnieć, że amoże to mieć miejsce nullpodczas implementacji other.method. PowracającyOptional sprawia, że ​​wywołujący nie mogą zapomnieć o tym przypadku, ponieważ sami muszą rozpakować obiekt.

Z tych powodów zalecamy używanie Optionaljako zwracanego typu dla metod, ale niekoniecznie w argumentach metody.

(Nawiasem mówiąc, jest to całkowicie zaczerpnięte z dyskusji tutaj ).

Louis Wasserman
źródło
3
+1 za świetne wyjaśnienie. Nie wiem, czy to jest inspiracja, ale dodałbym wskaźnik do typów opcji w SML, OCaml i F #, które mają wiele tej samej semantyki.
Adam Mihalcin
2
Zawsze wspaniale jest otrzymać odpowiedź od osoby, która była bezpośrednio zaangażowana. Dałbym +1 tylko za to. Dałoby +1 za świetną odpowiedź. (ale nie mogę.) Myślę, że jednym z nieodpartych powodów jest posiadanie tego jako wartości zwracanej, aby zmusić konsumentów nieznanej metody do podjęcia wysiłku, aby przyznać, że „nic” nie może zostać zwrócone. Nie podoba mi się jednak, jak jest nadużywany (a nawet nadużywany). np. czuję się naprawdę nieswojo, kiedy widzę, jak ludzie umieszczają opcje jako wartości w Mapach. Przywracanie mapy zerowej dla nieistniejącego / niezamapowanego klucza jest dobrze ugruntowanym paradygmatem ... Nie ma potrzeby, aby to bardziej skomplikować ...
RAY
9
Jest dobrze ugruntowane, że Mappowraca, nulljeśli klucz jest niezamapowany, ale pamiętaj, że jeśli to zrobisz map.put(key, null), map.containsKey(key)zwróci, trueale map.get(key)zwróci null. Optionalmoże być przydatny w wyjaśnianiu różnicy między przypadkiem „jawnie zamapowanym na wartość null” a przypadkiem „nieobecny na mapie”. Zgadzam się, że Optionalmożna to nadużyć, ale nie jestem jeszcze przekonany, że przypadek, który opisujesz, jest nadużyciem.
Louis Wasserman,
8
Aby użyć przestarzałej analogii, były kiedyś te gigantyczne rzeczy zwane „książkami telefonicznymi” :-) i gdybym poprosił cię o sprawdzenie czyjegoś numeru, i powiedziałeś „nie ma numeru dla tej osoby”, powiedziałbym: „co ty znaczy, że są tam z zastrzeżonym numerem lub po prostu nie mogłeś znaleźć dla nich wpisu? ” Te dwie możliwe odpowiedzi ładnie mapują się odpowiednio na Optional.absent () i null. Optional.absent () jest ostatecznym „pozytywnym negatywem”.
Kevin Bourrillion
3
@RAY: W pewnym sensie Optional<T> jest to wartość „znaleziona, ale nieprawidłowa”. A dokładniej, Optional<T>jest to sposób na udekorowanie dowolnego typu Tdodatkową wartością „znalezioną, ale nieprawidłową” - tworzenie nowego typu poprzez połączenie dwóch istniejących typów. Jeśli masz sto klas, utworzenie oddzielnej wartości „znalezionej, ale niepoprawnej” dla każdej z nich Optional<T>może być kłopotliwe , ale może z łatwością działać dla wszystkich.
Daniel Pryden,
9

Naprawdę wygląda jak Maybewzór Monad od Haskella.

Warto przeczytać następującą, Wikipedię Monad (programowanie funkcjonalne) :

I odczytać z opcjonalnego do Monady z guawa na Kerflyn Blog, który omawia temat opcjonalnego guawa używany jako monady:


Edycja: w Javie8 jest wbudowana opcja, która ma operatory monadyczne, takie jak flatMap. To był kontrowersyjny temat, ale w końcu został wdrożony.

Zobacz http://www.nurkiewicz.com/2013/08/optional-in-java-8-cheat-sheet.html

public Optional<String> tryFindSimilar(String s)  //...

Optional<Optional<String>> bad = opt.map(this::tryFindSimilar);
Optional<String> similar =       opt.flatMap(this::tryFindSimilar);

flatMapOperator jest niezbędna, aby umożliwić monadycznego operacji i pozwala na łatwe połączeń łańcuchowych, że wszystkie powrotne opcjonalne wyników.

Pomyśl o tym, gdybyś użył mapoperatora 5 razy, skończyłbyś z Optional<Optional<Optional<Optional<Optional<String>>>>>, a użycie flatMapdałoby ciOptional<String>

Od Java8 wolałbym nie używać opcjonalnego Guavy, który jest mniej wydajny.

Sebastien Lorber
źródło
6

Jednym z dobrych powodów, aby go używać, jest to, że sprawia, że ​​wartości null są bardzo znaczące. Zamiast zwracać wartość null, która może oznaczać wiele rzeczy (np. Błąd, awaria, pusta wartość itp.), Możesz wstawić „nazwę” do wartości null. Spójrz na ten przykład:

zdefiniujmy podstawowe POJO:

class PersonDetails {

String person;
String comments;

public PersonDetails(String person, String comments) {
    this.person = person;
    this.comments = comments;
}

public String getPerson() {
    return person;
}


public String getComments() {
    return comments;
}

}

Skorzystajmy teraz z tego prostego POJO:

public Optional<PersonDetails> getPersonDetailstWithOptional () {

  PersonDetails details = null; /*details of the person are empty but to the caller this is meaningless,
  lets make the return value more meaningful*/


    if (details == null) {
      //return an absent here, caller can check for absent to signify details are not present
        return Optional.absent();
    } else {
      //else return the details wrapped in a guava 'optional'
        return Optional.of(details);   
    }
}

Teraz unikajmy używania null i wykonuj nasze sprawdzenia z opcją Optional, więc jest to sensowne

public void checkUsingOptional () {

    Optional<PersonDetails> details = getPersonDetailstWithOptional();

    /*below condition checks if persons details are present (notice we dont check if person details are null,
    we use something more meaningful. Guava optional forces this with the implementation)*/
    if (details.isPresent()) {

      PersonDetails details = details.get();

        // proceed with further processing
        logger.info(details);

    } else {
        // do nothing
        logger.info("object was null"); 
    }

    assertFalse(details.isPresent());
}

więc ostatecznie jest to sposób na nadanie zerom znaczenia i mniej dwuznaczności.

j2emanue
źródło
4

Najważniejszą zaletą Optional jest to, że dodaje więcej szczegółów do kontraktu między implementatorem a obiektem wywołującym funkcję. Z tego powodu jest przydatna zarówno dla parametrów, jak i typu zwracanego.

Jeśli przyjmiesz konwencję, aby zawsze mieć Optionaldla możliwych obiektów zerowych, dodasz więcej wyjaśnień do przypadków, takich jak:

  1. Optional<Integer> maxPrime(Optional<Integer> from, Optional<Integer> to)

    Umowa tutaj jasno określa, że ​​istnieje szansa, że ​​wynik nie zostanie zwrócony, ale także pokazuje, że będzie działać z fromi tojako nieobecny.

  2. Optional<Integer> maxPrime(Optional<Integer> from, Integer to)

    Kontrakt określa, że ​​from jest opcjonalny, więc nieobecna wartość może mieć specjalne znaczenie, takie jak początek od 2. Mogę się spodziewać, że wartość null toparametru zgłosi wyjątek.

Tak więc dobrą częścią korzystania z Optional jest to, że kontrakt stał się zarówno opisowy (podobnie jak w przypadku @NotNulladnotacji), ale także formalny, ponieważ musisz napisać kod, .get()aby sobie z nim poradzić Optional.

raisercostin
źródło
Po co używać opcjonalnej <Listy>, skoro pusta lista byłaby czystsza?
RAY
Po powrocie wybrałem zły przykład. Dzięki kolekcjom możesz ustawić konwencję, aby zawsze zwracała pustą kolekcję zamiast wartości null. Jeśli używana jest ta konwencja, nie jest wymagana opcja dla kolekcji. Opcjonalny może być postrzegany jako zbiór z zerem lub jednym elementem, więc nie ma potrzeby umieszczania go wokół innego zbioru.
raisercostin
2
w zależności od kontekstu może wystąpić znacząca różnica między listą zawierającą 0 pozycji a listą nieobecną.
plazma147