Fehlerbehandlung mittels Optionals
Mit Exceptions stellt uns Java ein Sprachmittel zur Verfügung, um auf Ausnahmesituationen zu reagieren. Manchmal passieren jedoch auch Fehler, die erwartet werden können. Folgendes Beispiel zeigt eine solche Situation:
class Employees {
public Employee getEmployeeWithId(int id) { /* ...*/ }
}
Wenn in dieser Methode kein Mitarbeiter mit der entsprechenden id
gefunden wird, muss dies dem Aufrufer irgendwie angezeigt werden.
Eine Möglichkeit wäre es, hier eine Exception zu werfen.
Exceptions sind hier jedoch nicht das richtige Mittel.
Dies ist eine Situation, mit welcher der Aufrufer rechnen muss.
Deshalb sollte auch dieser Fall als Teil der normalen Programmlogik behandelt werden.
Eine schlechte Option: Rückgabe von null
Früher hat man dieses Problem oft damit gelöst, dass die Methode in solchen Fällen null
zurückgegeben hat.
Der Aufrufer konnte dann dieses Problem beheben, indem er auf null
testet:
int id = 5;
Employee e = employees.getEmployeeWithId(id);
if (e != null) {
// Normale Programmlogik
} else {
// Logik für den Fall, dass Employee nicht gefunden wurde.
}
Diese, leider sehr verbreitete Praxis, hat riesige Nachteile.
Der Aufrufer kann beim Aufruf der Methode nicht wissen, dass die Methode fehlschlagen kann.
Es ist nirgends in der Methodensignatur ersichtlich.
Auch Java hat diese Information nicht und kann deshalb nicht forcieren, dass dieser Fehlerfall behandelt wird.
Falls der Fehler nicht behandelt wird, führt dies zu einem Laufzeitfehler.
Dies ist die berühmt und berüchtigte NullPointerException
, die leider allen Programmierer:innen bekannt ist.
Eine bessere Lösung bietet die Klasse Optional
.
Die bessere Option: Optional
Java bietet mit der Klasse Optional
eine einfache Lösung für dieses Problem.
Interessierte finden in der API Dokumentation mehr Details.
Optional
ist im Wesentlichen wie folgt definiert:
class Optional<T> {
T value = null;
private Optional(T value) {
this.value = value;
}
static <T> Optional<T> of(T value) {
return new Optional<T>(value);
}
static <T> Optional<T> empty() {
return new Optional<T>(null);
}
boolean isPresent() {
return this.value != null;
}
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
}
Wir sehen, dass ein Optional
entweder durch die Methode Optional.empty()
oder Optional.of()
kreiert werden kann.
Im ersten Fall wird ein Optional erzeugt, dass keinen Wert enthält, im zweiten Fall wird der übergebene Wert gespeichert.
Mittels der isPresent
Methode kann abgefragt werden, ob das Optional einen Wert enthält.
Die get
Methode wird benutzt, um den Wert zu erhalten.
In unserem Beispiel würde Optional
wie folgt eingesetzt werden:
class Employees {
public Optional<Employee> getEmployeeWithId(int id) { /* ...*/ }
}
int id = 5;
Optional<Employee> maybeAnEmployee = employees.getEmployeeWithId(id);
if (maybeAnEmployee.isPresent()) {
Employee employee = maybeAnEmployee.get()
// Normale Programmlogik
} else {
// Logik für den Fall, dass Employee nicht gefunden wurde.
}
Diese Lösung hat grosse Vorteile.
Erstens sehen wir an der Methodensignatur Optional<Employee> getEmployeeWithId(int id)
, dass diese Methode eventuell keinen Wert zurückliefert.
Zudem können wir die Fehlerbehandlung gar nicht versehentlich vergessen, da wir ja nicht direkt auf den Wert employee
zugreifen können, ohne zuerst get()
aufzurufen.
Der Java-Compiler wird uns anderenfalls eine Fehlermeldung geben, und uns daran erinnern, dass wir noch eine Fehlerbehandlung machen sollten.
Wenn wir null
als Fehlerwert zurückgeben, erhalten wir also oft einen Laufzeitfehler.
Im Gegensatz dazu sehen wir bei Optional
den möglichen Fehler bereits zur Entwicklungszeit.
Entsprechend werden unsere Programme mit diesem Ansatz viel verlässlicher.
Haben Sie Fragen oder Bemerkungen? Schreiben Sie diese doch ins Forum.