Kiedy wypakować Optional

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

2 myśli na temat “Kiedy wypakować Optional

  1. Bardzo ciekawe uwagi. Sęk w tym że Optional w większości projektów wygląda tak:
    public Optional<Person> findByLogin(String login){
    ...
    return Optional.ofNullable(person);
    }

    A potem tylko:

    private void login (){
    Optional<Person> optionalPerson = userService.findUserByLogin(login);
    if (optionalPerson.isPresent()) {
    Person person = optionalPerson.get();
    doSomethingMoreWith(person);
    }
    }

    Kurtyna.

  2. @eL i dlatego właśnie nie należy rozpakowywać Optional. Wiele osób korzysta, ale mało kto rozumie jak skorzystać. Trochę jak z przerzutką w rowerze. Każdy ma, każdy korzysta, ale efektywne wykorzystanie to już zupełnie inna bajka.

Napisz odpowiedź

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax