Optionals: Mal hat man’s und mal nicht (2 / 3)

Im ersten Teil des Artikels haben wir uns angesehen wie traditionell die Aussage „ich habe keinen Wert“ in Java modelliert und behandelt wird. Die null Checks, mit denen auf null Werte reagiert wird, werden dabei recht schnell etwas unübersichtlich. Deshalb betrachten wir nun wie die mit Java 8 eingeführten Optionals beim Umgang mit diesem Problem helfen.

Prinzip: Nur wirklich optionale Werte können null werden

Beim Modellieren einer Klasse gibt es häufig Werte, die immer da sein müssen und Werte, die nicht unbedingt immer vorhanden sind. Um lesbaren und wartbaren Code herzustellen können wir diese Situationen direkt im Code kenntlich machen.

class Fahrersitz {
};

class Anhaengerkupplung {
  private final String name;

  public Anhaengerkupplung(String name) {
    this.name = name;
  }
};

class Auto {
  private final Fahrersitz fahrersitz;
  private Anhaengerkupplung anhaengerkupplung;

  public Auto(Fahrersitz fahrersitz) {
    this.fahrersitz = fahrersitz;
  }

  public void setAnhaengerkupplung(Anhaengerkupplung anhaengerkupplung) {
    this.anhaengerkupplung = anhaengerkupplung;
  }
}

Die Klasse Auto soll stark vereinfacht ein Auto modellieren. Jedes Auto in unserer Modellwelt hat einen Fahrersitz. Aber nicht jedes Auto hat eine Anhängerkupplung. Das drücken wir in Java aus, indem wir den Fahrersitz final, und damit unveränderlich, machen und den Verwender der Klasse zwingen den Fahrersitz direkt im Konstruktor mit zu übergeben. Ebenso hat eine Anhängerkupplung immer einen Namen, den wir im Konstruktor mit übergeben müssen.

Die Anhängerkupplung ist hingegen optional. Manche Autos haben eine, manche nicht. Dieser Wert wird deshalb nicht über den Konstruktor übergeben, sondern kann später über einen Setter gesetzt werden. Solange keine Anhängerkupplung definiert wird, bleibt die Instanzvariable null.

Fahrersitz fahrersitz = new Fahrersitz();
Auto auto = new Auto(fahrersitz);
// Hier haben wir ein Auto mit einem Fahrersitz und ohne Anhängerkupplung
Anhaengerkupplung anhaengerkupplung = new Anhaengerkupplung("Meine super Kupplung");
auto.setAnhaengerkupplung(anhaengerkupplung);
// Jetzt hat das Auto auch eine Anhängerkupplung

Wir können nun also schon sehr deutlich formulieren, welche Attribute ein Objekt einer Klasse unbedingt haben muss und welche optional sind. Aber wie greifen wir nun auf optionale Attribute zu, wenn wir die null Checks vermeiden möchten?

Prinzip: Optionale Werte sind optional

Auch mit den neuen Optionals wird die Information „dieser Wert ist derzeit nicht vorhanden“ durch null gekennzeichnet. Aber der Zugriff darauf erfolgt nun über ein Optional. Anstelle eines klassischen Getters schreiben wir eine Methode, die ein Optional zurückliefert:

  public Optional<Anhaengerkupplung> anhaengerkupplung() {
    return Optional.ofNullable(anhaengerkupplung);
  }

Diese Methode erzeugt ein Objekt der Klasse Optional und gibt diesem Objekt den Wert, den wir kennen (also entweder eine Anhängerkupplung oder eben null) mit. Die Klasse Optional dient dazu ganz explizit auszudrücken, dass ein Wert optional ist und sehr deutlich darzustellen, ob wir einen Wert kennen oder nicht. Ein Optional ist selbst nie null und kann uns immer mitteilen, ob es einen Wert enthält oder nicht. Wir können nun also das Ergebnis der Methode ohne null-Check verwenden:

Optional<Anhaengerkupplung> anhaengerkupplungOptional = auto.anhaengerkupplung();
String name;
if(anhaengerkupplung.isPreset()) {
  Anhaengerkupplung anhaengerkupplung = anhaengerkupplungOptional.get();
  name = anhaengerkupplung.getName();
} else {
  name = "-";
}

Wenn man den Code so liest, sieht er eigentlich gar nicht nach einem Gewinn aus. Statt des null-Checks müssen wir nun einen anderen Check durchführen. Und das Optional Objekt müssen wir nun (mit dem get() Aufruf) auch noch auspacken. Das sieht überhaupt nicht besser aus als mit einem einfachen null-Check.

Aber schauen wir uns die Sache weiter an…

Optional<Anhaengerkupplung> anhaengerkupplungOptional = auto.anhaengerkupplung();
String name;
if(anhaengerkupplung.isPreset()) {
  Optional<String> nameOptional = anhaengerkupplungOptional.map(Anhaengerkupplung::getName);
  name = nameOptional.get();
} else {
  name = "-";
}

Hier haben wir das Auspacken des Optionals modifiziert. Der map() Methode übergeben wir eine Transformationsmethode. In diesem Fall übergeben wir eine Referenz auf die Methode getName() der Klasse Anhaengerkupplung. Die Wirkung ist interessant: Wenn das Optional tatsächlich eine Anhaengerkupplung enthält, wird für dieses Anhaengerkupplung Objekt die getName() Methode aufgerufen und ein neues Optional mit dem Ergebnis dieser Methode erzeugt. Aus einem Optional<Anhaengerkupplung> haben wir damit ein Optional<String> gemacht. Falls das Optional keinen Wert enthält, wird die getName() Methode nicht aufgerufen; und das Optional bleibt einfach leer.

Und nun vereinfachen wir das Ganze noch kräftig:

String name = auto.anhaengerkupplung().map(Anhaengerkupplung::getName).orElse("-");

Neu ist hier das orElse(). Falls das Optional einen Wert enthält, packt orElse() diesen Wert (mittels get()) aus und liefert ihn. Aus einem Optional<String> wird damit also der enthaltene String ausgepackt. Ganz ohne Aufruf von get(). Und falls das Optional keinen Wert enthält, wird einfach der Wert zurückgeliefert, den wir dem orElse() mitgeben.

Im Endeffekt erhalten wir in name also entweder den Wert der Instanzvariable name aus der Anhängerkupplung des auto Objekts oder einfach den String „-„, falls das Auto keine Anhängerkupplung hat. Und das alles nun ganz ohne null-Checks. Jetzt wird es interessant, oder?

Im nächsten Teil des Artikels loten wir die Möglichkeiten von Optionals weiter aus.

Ein Kommentar

  1. Thorsten Claus sagt:

    Gut erklärt.
    Nützlich als Argumentationshilfe für die Kollegen, die an das alte If.. else hängen.

    Danke für die Arbeit.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert