Kiedy wypakować Optional
Odpowiedź brzmi najlepiej nigdy.
Pandora już raz rozpakowała Optional
a i do dziś dzieci się o tym uczą w szkole. Zresztą można sobie, to opisać w następujący sposób.
Tygrys w klatce
Wyobraźmy sobie, że mamy do dyspozycji Tygrysa. Niech ma na imię Bonawentura.
Listing 1. Nasz tygrys
public class Tiger {
private final String name;
private State state;
public Tiger(String name) {
this.name = name;
this.state = State.HAPPY;
}
// tu jest wielka zagadka zwana psychiką tygrysa – nie znamy dokładnego zachowania!
public enum State {
HAPPY, HUNGRY, ANGRY, SLEEPY;
}
}
Bonawentura, jak każdy rasowy samiec może być szczęśliwy, głodny, zły albo śpiący. Rodzi się on szczęśliwy, choć zestresowany (bo głodny):
Listing 2. Cud narodzin
public class TigerInTheCage {
public static void main(String[] args) {
Tiger bonawentura = new Tiger("Bonawentura");
System.out.println(bonawentura);
}
}
Pojawia się oczywiście mama tygrys i karmi naszego bohatera:
Listing 3. Mama tygrys na ratunek
public class TigerInTheCage {
static MommyTiger momy = new MommyTiger();
public static void main(String[] args) {
Tiger bonawentura = new Tiger("Bonawentura");
System.out.println(bonawentura);
bonawentura.giveFood(momy);
System.out.println(bonawentura);
}
}
Wiadomo, samiec głodny, to zły, a jak najedzony, to śpi. A jak śpi, to jest mniej czujny i daje się zaskoczyć przez kłusownika:
Listing 4. Zły kłusownik
class Poacher implements Protector {
@Override
public Optional<Tiger> catchThem(Tiger tiger) {
System.out.println("I catch the tiger!!!");
tiger.goingCrazy(); // no to się ktoś wkurzył
return Optional.ofNullable(tiger); // albo i nie
}
@Override
public boolean isMom() {
return false;
}
}
Listing 5. I samo zdarzenie
public class TigerInTheCage {
static MommyTiger momy = new MommyTiger();
static Poacher poacher = new Poacher();
public static void main(String[] args) {
Tiger bonawentura = new Tiger("Bonawentura");
System.out.println(bonawentura);
bonawentura.giveFood(momy);
System.out.println(bonawentura);
Optional<Tiger> tigerInBag = poacher.catchThem(bonawentura);
System.out.println(bonawentura);
}
}
I teraz trzeba jakoś Bonawenturę spacyfikować. Kłusownik zna się na tygrysach, a zatem wrzuca coś do worka i nie ujawnia się. Dodatkowo zabezpieczył się odpowiednią klatką:
Listing 6. Karmienie tygrysa – robisz to dobrze.
public class TigerInTheCage {
static MommyTiger momy = new MommyTiger();
static Poacher poacher = new Poacher();
public static void main(String[] args) {
Tiger bonawentura = new Tiger("Bonawentura");
System.out.println(bonawentura);
bonawentura.giveFood(momy);
System.out.println(bonawentura);
Optional<Tiger> tigerInBag = poacher.catchThem(bonawentura);
System.out.println(bonawentura);
Try.of(() -> tigerInBag.map(t -> {
t.giveFood(null);
return t;
}));
System.out.println(bonawentura);
}
}
Bonawentura jak widać, znowu podszedł spać, bo wrzucił coś na ząb. Kłusownik nie próżnował i sprzedał go na Oledroggo niejakiej Kaleszi, ta postanowiła obudzić Bonawenturę, a ten oczywiście się wkurwił:
Listing 7. Kaleszi nie powinna tego robić.
public class TigerInTheCage {
static MommyTiger momy = new MommyTiger();
static Poacher poacher = new Poacher();
static Kaleszi kaleszi = new Kaleszi();
public static void main(String[] args) {
Tiger bonawentura = new Tiger("Bonawentura");
System.out.println(bonawentura);
bonawentura.giveFood(momy);
System.out.println(bonawentura);
Optional<Tiger> tigerInBag = poacher.catchThem(bonawentura);
System.out.println(bonawentura);
Try.of(() -> tigerInBag.map(t -> {
t.giveFood(null);
return t;
}));
System.out.println(bonawentura);
kaleszi.playWith(bonawentura);
System.out.println(bonawentura);
}
}
Oczywiście, za telefon i dzwoni do kłusownika co dalej. Ten mówi, nakarm go i po sprawie. Zatem karmimy:
Listing 8. Karmienie tygrysa – robisz to źle.
public class TigerInTheCage {
static MommyTiger momy = new MommyTiger();
static Poacher poacher = new Poacher();
static Kaleszi kaleszi = new Kaleszi();
public static void main(String[] args) {
Tiger bonawentura = new Tiger("Bonawentura");
System.out.println(bonawentura);
bonawentura.giveFood(momy);
System.out.println(bonawentura);
Optional<Tiger> tigerInBag = poacher.catchThem(bonawentura);
System.out.println(bonawentura);
tigerInBag = Try.of(() -> tigerInBag.map(t -> {
t.giveFood(null);
return t;
}));
System.out.println(bonawentura);
tigerInBag.map(kaleszi::playWith);
System.out.println(bonawentura);
tigerInBag.get().giveFood(kaleszi);
//... happy endu nie będzie
}
}
No i ją zeżarł.
Podsumowanie
Optional
jest co do zasady kontenerem, który ma chronić nas przed NPE. Jest to szczególny przypadek problemu. Inne kontenery takie jak Try
chronią
nas przed całym spektrum problemów. Jeszcze inne, jak Future
pozwalają na bezpieczne przekształcanie wartości bez konieczności wykonywania operacji
blokujących. Zatem decydując się na zamknięcie jakiejś wartości w kontenerze, należy założyć, że wszelkie operacje będziemy wykonywać w ramach tego
kontenera. Rozpakowanie powinno nastąpić w miejscu, w którym wiemy, jak poradzić sobie z potencjalnymi fakapami. Na przykład w przypadku Optional
,
wiemy jak należy potraktować pustą wartość. Dla Try
mamy gotowy mechanizm obsługi błędów, a Future
najlepiej rozpakować w wątku, który może zostać
zablokowany i nie będzie to boleć.