Odpowiedź brzmi najlepiej nigdy.

Pandora już raz rozpakowała Optionala 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ć.