optional

Optional


Motivation

public User getUserById(int id) {
    for (User user : users)
        if (user.getId() == id)
            return user;
    return null;
}
  • return null notwendig 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 value oder wirft NoSuchElementException

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);