Optional
Motivation
public User getUserById(int id) {
for (User user : users)
if (user.getId() == id)
return user;
return null;
}
return nullnotwendig für Compiler- Probleme
null- für den Aufrufer unerwartet
Lösung: Exception
class NoSuchUserException extends Exception() { ... }
public User getUserById(int id) throws NoSuchUserException {
for (User user : users)
if (user.getId() == id)
return user;
throw new NoSuchUserException();
}
null✔️- für den Aufrufer unerwartet ✔️
- checked Exception 🤮
Lösung: RuntimeException
public User getUserById(int id) {
for (User user : users)
if (user.getId() == id)
return user;
throw new NoSuchElementException();
}
null✔️- für den Aufrufer unerwartet 😕
Lösung: Spezielles Objekt
class User {
public static final User NO_SUCH_USER = new User(...);
...
}
public User getUserById(int id) {
for (User user : users)
if (user.getId() == id)
return user;
return User.NO_SUCH_USER;
}
null✔️- für den Aufrufer unerwartet ✔️
- gefährlich ⚠️(Programm crasht nicht)
Lösung: @Nullable
public @Nullable User findUserById(int id) {
for (User user : users)
if (user.getId() == id)
return user;
return null;
}
null✔️- für den Aufrufer unerwartet ✔️
- IDE-abhängig 😕
- nicht standardisiert 😕
Lösung: Optional
public Optional<User> findUserById(int id) {
for (User user : users)
if (user.getId() == id)
return Optional.of(user);
return Optional.empty();
}
null✔️- für den Aufrufer unerwartet ✔️
- standard
Optional<T>
public final class Optional<T> {
private final T value;
...
}
Container, der vielleicht null enthält
Basics
Optional<T> of(T value)- erzeugt nichtleeres
Optional - kapselt
value Optional<T> empty()- erzeugt leeres
Optional Optional<T> ofNullable(T value)- leer, falls
value == null T get()- liefert gespeicherten
valueoder wirftNoSuchElementException
Ablauf
Optional<T> optional = method();
if (optional.isPresent()) {
T value = optional.get();
doStuff(value);
} else {
handleError();
}
Optional<User> optionalUser = findUserById(42);
if (optionalUser.isPresent()) {
User user = optionalUser.get();
}
Diese Vorgangsweise klappt immer, geht aber besser
Best practice
- Returnwert:
Optional - Variable:
null - Collections: leere Collection
- Arrays: leeres Array
- Parameter von Methoden: Methode überladen
Functional Interfaces
Predicate<T>
public interface Predicate<T> {
boolean test(T t);
}
Testet, ob eine Bedingung erfüllt ist
Predicate<String> isEmpty = String::isEmpty;
Predicate<Integer> isPositive = i -> i > 0;
Predicate<Employee> isAvailable = Employee::isAvailable;
Supplier<T>
public interface Supplier<T> {
T get();
}
Liefert ein Objekt
Supplier<Employee> newEmployee = Employee::new;
final Random random = new Random();
Supplier<Integer> randomInt = random::nextInt;
Supplier<Integer> randomLottoNumber = () -> random.nextInt(45) + 1;
Consumer<T>
public interface Consumer<T> {
void accept(T t);
}
Führt eine void-Methode mit dem Objekt aus
Consumer<String> print = System.out::print;
Consumer<Collection<?>> clear = Collection::clear;
Consumer<Employee> dismiss = Employee::dismiss;
Function<T, R>
public interface Function<T, R> {
R apply(T t);
}
Transformiert Daten von T nach R
Function<String, Integer> length = String::length;
Function<Integer, Double> cubicRoot = i -> Math.pow(i, 1.0 / 3);
Function<Employee, Project> getProject = Employee::getProject;
public interface UnaryOperator<T> extends Function<T, T> {
}
Advanced
public final class Optional<T> {
...
public void ifPresent(Consumer<? super T> action)
public void ifPresentOrElse(Consumer<? super T> action,
Runnable emptyAction)
public Optional<T> filter(Predicate<? super T> predicate)
public <U> Optional<U> map(Function<? super T, ? extends U> mapper)
public T orElse(T other)
public T orElseThrow()
public <X extends Throwable> T orElseThrow(
Supplier<? extends X> exceptionSupplier) throws X
...
}
value wird von value.method() konsumiert
if (optionalUser.isPresent()) {
User user = optionalUser.get();
user.remove();
} else
handleError();
optionalUser.ifPresent(User::remove);
optionalUser.ifPresentOrElse(User::remove, this::handleError);
Returnwert von value.method() ist Ziel
if (optionalUser.isPresent()) {
User user = optionalUser.get();
String name = user.getName();
}
Optional<String> optionalName = optionalUser.map(User::getName);
empty -> empty
Defaultwert falls empty
User user;
if (optionalUser.isPresent()) {
user = optionalUser.get();
} else
user = User.NO_SUCH_USER;
User user = optionalUser.orElse(User.NO_SUCH_USER);
Exception falls empty
if (optionalUser.isPresent()) {
User user = optionalUser.get();
} else
throw new NoSuchElementException();
User user = optionalUser.orElseThrow();
User user = optionalUser.orElseThrow(NoSuchUserException::new);