2016-06-27 40 views
9

Uwielbiam to, że optionals są teraz w standardowej bibliotece Java. Ale pojawia się jeden podstawowy problem, na który nie mam pojęcia, jak rozwiązać problem w najlepszy (najłatwiejszy do odczytania i zrozumienia, najładniejszy i najkrótszy) sposób:Jak powrócić, gdy opcja jest pusta?

Powrót z metody, gdy opcjonalny jest pusty?

Szukam ogólnego rozwiązania, które działa dla różnych kombinacji liczb opcji i rozmiarów bloków kodu.

W poniższych przykładach postaram się pokazać, co mam na myśli:

void m1() { 
    // When I get an optional: 
    Optional<String> o = getOptional(); 

    // And want to return if it's empty 
    if (!o.isPresent()) return; 

    // In the whole rest of the method I have to call Optional.get 
    // every time I want the value: 
    System.out.println(o.get()); 

    // Which is pretty ugly and verbose! 
} 


void m2() { 
    // If I instead return null if a value is absent: 
    String s = getNullabe(); 
    if (s == null) return; 

    // Then I can use the value directly: 
    System.out.println(s); 
} 

To pytanie jest o tym, jak uzyskać dobry aspekt obu powyższych przykładach: typ bezpiecznie opcjonalnego i zwięzłości typów nullable.

Reszta przykładów ilustruje to bardziej.

void m3() { 
    // If I on the other hand want to throw on empty that's pretty and compact: 
    String s = getOptional() 
     .orElseThrow(IllegalStateException::new); 

    System.out.println(s); 
} 

void m4() { 
    Optional<String> o = getOptional(); 
    if (!o.isPresent()) return; 

    // I can of course declare a new variable for the un-optionalised string: 
    String s = o.get(); 

    System.out.println(s); 

    // But the old variable still remains in scope for the whole method 
    // which is ugly and annoying. 
    System.out.println(o.get()); 
} 


void m5() { 
    // This is compact and maybe pretty in some ways: 
    getOptional().ifPresent(s -> { 
     System.out.println(s); 

     // But the extra level of nesting is annoying and it feels 
     // wrong to write all the code in a big lambda. 

     getOtherOptional().ifPresent(i -> { 
      // Also, more optional values makes it really weird and 
      // pretty hard to read, while with nullables I would 
      // get no extra nesting, it would looks good and be 
      // easy to read. 
      System.out.println("i: " + i); 

      // It doesn't work in all cases either way. 
     }); 
    }); 
} 


Optional<String> getOptional() { 
    throw new UnsupportedOperationException(); 
} 

Optional<Integer> getOtherOptional() { 
    throw new UnsupportedOperationException(); 
} 

String getNullabe() { 
    throw new UnsupportedOperationException(); 
} 

Jak mogę wrócić z metody, jeżeli opcja jest pusta, bez konieczności korzystania get w pozostałej części metody, bez deklarowania dodatkową zmienną i bez dodatkowych poziomów zagnieżdżenia bloku?

Albo, jeśli nie można tego wszystkiego uzyskać, jaki jest najlepszy sposób poradzenia sobie z tą sytuacją?

+0

Naprawdę nie ma nic złego w 'if (optional.isPresent()) {...}'. 'Opcjonalne' dodaje więcej wartości semantycznej niż' null', więc misja została wykonana. Czytelność i zwięzłość często stanowią dobrą równowagę. "Ugly" i "verbose" są tutaj takimi przesadami. – Radiodef

+0

@Lii, jeśli interesuje Cię bardziej ogólny przypadek (różne typy opcji i operacji), dodaj go do swojego pytania. Jest to inny przypadek użycia, jeśli masz te same typy i te same operacje. – user140547

+1

to wszystkie szwy jak przykłady, w których użycie Opcjonalnego nie jest najlepszym rozwiązaniem ... –

Odpowiedz

11

Można użyć orElse(null):

String o = getOptional().orElse(null); 
if (o == null) { 
    return; 
} 
+2

Moja pierwsza myśl brzmiała: "Meh, wracam do używania nullables."Ale w drugiej chwili może to nie jest takie złe, ponieważ potencjalna nieobecność wartości jest nadal widoczna w typie' getOptional', a łańcuch zerowalny jest używany w małym zasięgu, gdzie jest jasne, co się dzieje. – Lii

+0

Ah, teraz próbowałem tego w jakimś prawdziwym kodzie i lubię to coraz więcej! Już pół tuzina 'get's zniknęło z mojego kodu. Dziękuję! – Lii

+0

naprawdę? I czemu nie dodać jeszcze jednego poziomu:' boolean noData = (o == null); if (noData) {return;} ' –

5

ifPresent że używasz nie wymaga, aby utworzyć nowy lambda, można po prostu użyć odwołania Metoda:

getOptional().ifPresent(System.out::println); 

ten tak naprawdę nie rozwiązuje przypadku, w którym chcesz warunkować na obecność dwóch opcji. Ale jako alternatywa dla

// And want to return if it's empty 
if (!o.isPresent()) return; 

dlaczego nie po prostu odwrócić sytuację, która działa dobrze w przypadku zagnieżdżonych też? Nie ma potrzeby, aby powrót wyraźne:

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

Jednak tego rodzaju przypadek użycia sugeruje, że nie jesteś naprawdę korzystają z Opcjonalnie, w przeciwieństwie do wartości pustych. Ogólnie rzecz biorąc, jeśli używasz metody isPresent i get, wówczas opcja Opcjonalnie może nie dostarczyć Ci aż tyle (oprócz tego, że zmusza Cię do rozważenia przypadku, w którym brakuje wartości). Używanie ifPresent, map, filtrów i innych "bardziej funkcjonalnych" metod może być bardziej typowymi zastosowaniami dla wartości Opcjonalnej.


Ale w żadnym wypadku nie zwracaj wartości zerowej, gdy obiecujesz opcję opcjonalną. Mimo, że zwracanie wartości null podczas oczekiwania na obiekt jest całkowicie legalne, punktem Opcjonalnym jest właśnie unikanie sprawdzania zerowej wartości.Więc nie rób:

Optional<String> getOptional() { 
    return null; 
} 

ale zamiast zrobić:

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

Inaczej skończyć się zrobić:

Optional<String> o = getOptional(); 
if (o != null && o.isPresent()) { 
    // ... 
} 

który jest tak naprawdę robi to samo coś takiego dwa razy. Użyj opcji opcjonalnej lub użyj wartości zerowej, ale nie rób tego jednocześnie!

+0

Często blok kodu do wykonania w lambda jest dość duży, więc nie jest to opcja przez większość czasu. – Lii

+0

@Lii Jeśli jest to duży blok kodu, to całkiem możliwe, że powinien zostać wyodrębniony do własnej metody, która mogłaby być następnie łatwo przekazana jako odniesienie do metody. –

+0

Heh, sugerujesz, że używam zarówno zagnieżdżania, jak i połączeń 'get', kiedy chcę tego uniknąć! Myślę, że znacznie wyraźniej jest rozpocząć metodę z kilkoma kontrolami warunków wstępnych, a następnie wrócić, jeśli się nie powiodą. To sprawia, że ​​czytelnikom staje się natychmiast jasne, co się stanie, jeśli kontrole się nie powiodą, i może zapisać kilka poziomów zagnieżdżenia dla całej pozostałej części metody. – Lii

0

Nie sądzę co pytasz jest rzeczywiście możliwe, ale chciałbym zaproponować tylko biorąc cały kod, który działa bezpośrednio na String i zawinąć go w funkcji. Więc funkcja staje się coś takiego:

void m4() { 
    Optional<String> o = getOptional(); 
    if (!o.isPresent()) return; 

    doThings(o.get()); 
} 

void doThings(String s){ 
    System.out.println(s); 
    //do whatever else with the string. 
} 

ten sposób masz tylko ciąg w zakresie i nie trzeba zadzwonić .get() każdym razem chcesz uzyskać do niego dostęp.

+1

Hm. Ale potem muszę przekazać wszystkie argumenty do 'm4' wraz z' doThing' również. I przez większość czasu nie chcę dzielić mojego rozwiązania na metody oparte tylko na tym, że pracuję z opcjonalnym. – Lii

+1

@Lii Nie, nie musisz wszystkich przekazywać; musisz tylko przekazać te, które ** doThings ** * potrzebuje *. Jedną z twoich obaw w tym pytaniu było to, że niektóre obiekty miały znacznie większy zakres niż to konieczne. Przez refaktoryzację do innej metody i przekazywanie jej wartości można uniknąć szerszych zakresów. –

+0

Jednak jeśli jest to tylko jedna wartość (ciąg znaków); zwrot "if (! o.isPresent())" wydaje się zbędny. Całe ciało metody 'm4' może być po prostu:' getOptional(). IfPresent (this :: doThings); '. –

6

Można użyć ifPresent i map metody zamiast, jeśli funkcja jest nieaktualna i trzeba zrobić skutków ubocznych można użyć ifPresent,

optional.ifPresent(System.out::println); 

Jeśli inny zwrot metoda opiera się na opcjonalnym niż ta metoda może trzeba zwracać Opcjonalnie, jak również i użyć metody mapie

Optional<Integer> getLength(){ 
    Optional<String> hi = Optional.of("hi"); 
    return hi.map(String::length) 
} 

Większość czasu gdy dzwonisz isPresent i get, niewłaściwie używasz Optional.

+0

Zobacz metodę 'm5' w przykładzie, ma to na celu wykazanie, dlaczego nie chcę tego zrobić. Myślę, że używanie 'ifPresent' jest w porządku, gdy robisz jedną krótką rzecz. Ale jeśli robisz wiele rzeczy i/lub masz kilka opcji, to poziom zagnieżdżania szybko sprawia, że ​​jest brzydki i trudny do odczytania. – Lii

+0

@Lii możesz przenosić/dzielić długie bloki w nowych metodach, prawda? –

+0

@CarlosHeuberger: Tak, ale często nie chcę decydować, jakie metody strukturyzować mój program na podstawie tylko wtedy, gdy mam do czynienia z opcjonalną wartością. I często metoda najpierw sprawdza kilka warunków wstępnych i kończy się, jeśli się nie utrzymają. Czasami te warunki wstępne są takie, że opcjonalna wartość nie jest pusta. – Lii