Optional im Quadrat


Ich wünschte wirklich, dass es in Java mehr Unterstützung für „monadisches Zeug“ im Doppelpack gäbe. Angefangen von Pair, zu Optionals, und bis zu Streams. Pair ist natürlich langweilig, und x-mal verfügbar, aber hier mal ein Schnellschuss, wie ich mir eine Helferklasse für zwei Optionals vorstellen würde:

import java.util.Objects;
import java.util.Optional;
import java.util.function.*;

public class Optional2<A, B> {

    private final Optional<A> first;
    private final Optional<B> second;

    private Optional2(Optional<A> first, Optional<B> second) {
        this.first = Objects.requireNonNull(first);
        this.second = Objects.requireNonNull(second);
    }

    public static <A, B> Optional2<A, B> empty() {
        return of(Optional.empty(), Optional.empty());
    }

    public static <A, B> Optional2<A, B> of(Optional<A> first, Optional<B> second) {
        return new Optional2<>(first, second);
    }

    public static <A, B> Optional2<A, B> ofNullable(A a, B b) {
        return new Optional2<>(Optional.ofNullable(a), Optional.ofNullable(b));
    }

    public static <A> Optional<A> unify(Optional2<A, A> optional2, BinaryOperator<A> op) {
        return unify(optional2.first, optional2.second, op);
    }

    public static <A> Optional<A> unify(Optional<A> first, Optional<A> second, BinaryOperator<A> op) {
        return first.map(
            a -> second.map(
                b -> op.apply(a, b)).orElse(a))
                    .map(Optional::of)
                    .orElse(second);
    }

    public Optional<A> first() {
        return first;
    }

    public Optional<B> second() {
        return second;
    }

    public Optional2<B, A> swap() {
        return of(second, first);
    }

    public <C> Optional<C> map(BiFunction<A, B, C> fn) {
        return first.flatMap(
            a -> second.map(
                b -> fn.apply(a, b)));
    }

    public <C> Optional2<C, B> mapFirst(BiFunction<A, B, C> fn) {
        return of(map(fn), second);
    }

    public <C> Optional2<C, B> mapFirst(Function<A, C> fn) {
        return of(first.map(fn), second);
    }

    public <C> Optional2<A, C> mapSecond(BiFunction<A, B, C> fn) {
        return of(first, map(fn));
    }

    public <C> Optional2<A, C> mapSecond(Function<B, C> fn) {
        return of(first, second.map(fn));
    }

    public <C> Optional<C> flatMap(BiFunction<A, B, Optional<C>> fn) {
        return first.flatMap(
            a -> second.flatMap(
                b -> fn.apply(a, b)));
    }

    public <C> Optional2<C, B> flatMapFirst(BiFunction<A, B, Optional<C>> fn) {
        return Optional2.of(flatMap(fn), second);
    }

    public <C> Optional2<C, B> flatMapFirst(Function<A, Optional<C>> fn) {
        return Optional2.of(first.flatMap(fn), second);
    }

    public <C> Optional2<A, C> flatMapSecond(BiFunction<A, B, Optional<C>> fn) {
        return Optional2.of(first, flatMap(fn));
    }

    public <C> Optional2<A, C> flatMapSecond(Function<B, Optional<C>> fn) {
        return Optional2.of(first, second.flatMap(fn));
    }

    public <C, D> Optional2<C, D> biFlatMap(BiFunction<A, B, Optional2<C, D>> fn) {
        return first.flatMap(
            a -> second.map(
                b -> fn.apply(a, b)
            )).orElseGet(Optional2::empty);
    }

    public Optional2<A, B> filter(BiPredicate<A, B> predicate) {
        return map(predicate::test).orElse(false) ? this
                   : Optional2.of(Optional.empty(), Optional.empty());
    }

    public Optional2<A, B> filterFirst(BiPredicate<A, B> predicate) {
        return map(predicate::test).orElse(false) ? this
                   : Optional2.of(Optional.empty(), second);
    }

    public Optional2<A, B> filterFirst(Predicate<A> predicate) {
        return Optional2.of(first.filter(predicate), second);
    }

    public Optional2<A, B> filterSecond(BiPredicate<A, B> predicate) {
        return map(predicate::test).orElse(false) ? this
                   : Optional2.of(first, Optional.empty());
    }

    public Optional2<A, B> filterSecond(Predicate<B> predicate) {
        return Optional2.of(first, second.filter(predicate));
    }

    public boolean bothPresent() {
        return first.isPresent() && second.isPresent();
    }

    public boolean bothEmpty() {
        return !first.isPresent() && !second.isPresent();
    }

    public Optional2<A, B> ifBothPresent(BiConsumer<A, B> consumer) {
        first.ifPresent(
            a -> second.ifPresent(
                b -> consumer.accept(a, b)));
        return this;
    }

    public Optional2<A, B> ifBothEmpty(Runnable runnable) {
        if (!first.isPresent() && !second.isPresent()) {
            runnable.run();
        }
        return this;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Optional2<?, ?> optional2 = (Optional2<?, ?>) o;
        return first.equals(optional2.first) &&
                   second.equals(optional2.second);
    }

    @Override
    public int hashCode() {
        return Objects.hash(first, second);
    }

    @Override
    public String toString() {
        return String.format("Optional2[%s,%s]",
            first.map(Object::toString).orElse("<empty>"),
            second.map(Object::toString).orElse("<empty>"));
    }
}

Die Benutzung sollte ziemlich selbsterklärend sein, deshalb spare ich mir Beispiele. Ansonsten ist nur noch zu erwähnen, dass ich stark in Versuchung war, die Klasse „Dopptional“ zu nennen – aber der Pun ist selbst für meine Verhältnisse zu mies…

Der Code als Gist

Werbeanzeigen

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden /  Ändern )

Google Foto

Du kommentierst mit Deinem Google-Konto. Abmelden /  Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden /  Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden /  Ändern )

Verbinde mit %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.