647 Stimmen

Java 8 Lambda-Funktion, die eine Ausnahme wirft?

Ich weiß, wie man eine Referenz auf eine Methode erstellt, die einen String-Parameter hat und einen int zurückgibt, es ist:

Function

Allerdings funktioniert das nicht, wenn die Funktion eine Ausnahme wirft, sagen wir, sie ist definiert als:

Integer myMethod(String s) throws IOException

Wie würde ich diese Referenz definieren?

6voto

TriCore Punkte 1681

Es sind bereits viele großartige Antworten hier veröffentlicht worden. Versuche nur, das Problem mit einer anderen Perspektive zu lösen. Das sind nur meine 2 Cent, bitte korrigiere mich, falls ich irgendwo falsch liege.

Throws-Klausel in FunctionalInterface ist keine gute Idee

Ich denke, es ist wahrscheinlich keine gute Idee, throws IOException zu erzwingen, aus folgenden Gründen

  • Dies scheint mir wie ein Anti-Pattern für Stream/Lambda. Die ganze Idee ist, dass der Aufrufer entscheidet, welchen Code er bereitstellt und wie er die Ausnahme behandelt. In vielen Szenarien ist die IOException möglicherweise nicht für den Kunden anwendbar. Zum Beispiel, wenn der Kunde den Wert aus dem Cache/Speicher abruft, anstatt tatsächliche E/A-Operationen durchzuführen.

  • Außerdem wird das Behandeln von Ausnahmen in Streams wirklich hässlich. Zum Beispiel so wird mein Code aussehen, wenn ich Ihre API benutze

               acceptMyMethod(s -> {
                    try {
                        Integer i = doSomeOperation(s);
                        return i;
                    } catch (IOException e) {
                        // try catch block because of throws clause
                        // in functional method, even though doSomeOperation
                        // might not be throwing any exception at all.
                        e.printStackTrace();
                    }
                    return null;
                });

    Hässlich, oder? Außerdem, wie ich bereits in meinem ersten Punkt erwähnt habe, dass die doSomeOperation-Methode möglicherweise keine IOException wirft (je nach Implementierung des Clients/Aufrufers), aber wegen der throws-Klausel in Ihrer FunctionalInterface-Methode muss ich immer den try-catch schreiben.

Was mache ich, wenn ich wirklich weiß, dass diese API IOException wirft

  • Dann verwirren wir wahrscheinlich FunctionalInterface mit typischen Schnittstellen. Wenn Sie wissen, dass diese API IOException wirft, wissen Sie höchstwahrscheinlich auch einige Standard-/abstrakte Verhaltensweisen. Ich denke, Sie sollten eine Schnittstelle definieren und Ihre Bibliothek (mit Standard-/abstrakter Implementierung) wie folgt bereitstellen

    public interface MyAmazingAPI {
        Integer myMethod(String s) throws IOException;
    }

    Aber das try-catch-Problem besteht immer noch für den Kunden. Wenn ich Ihre API im Stream verwende, muss ich immer noch die IOException in einem hässlichen try-catch-Block behandeln.

  • Bieten Sie eine standardmäßige stream-freundliche API wie folgt an

    public interface MyAmazingAPI {
        Integer myMethod(String s) throws IOException;
    
        default Optional myMethod(String s, Consumer exceptionConsumer) {
            try {
                return Optional.ofNullable(this.myMethod(s));
            } catch (Exception e) {
                if (exceptionConsumer != null) {
                    exceptionConsumer.accept(e);
                } else {
                    e.printStackTrace();
                }
            }
    
            return Optional.empty();
        }
    }

    Die Standardmethode nimmt das Consumer-Objekt als Argument, das für die Behandlung der Ausnahme verantwortlich ist. Jetzt aus der Sicht des Kunden wird der Code so aussehen

    strStream.map(str -> amazingAPIs.myMethod(str, Exception::printStackTrace))
                    .filter(Optional::isPresent)
                    .map(Optional::get).collect(toList());

    Schön, oder? Natürlich könnten Logger oder andere Behandlungslogik anstelle von Exception::printStackTrace verwendet werden.

  • Sie können auch eine Methode ähnlich wie https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html#exceptionally-java.util.function.Function- erstellen. Das bedeutet, dass Sie eine andere Methode freigeben können, die die Ausnahme aus dem vorherigen Methodenaufruf enthält. Der Nachteil ist, dass Sie jetzt Ihre APIs zustandsbehaftet machen, was bedeutet, dass Sie Thread-Sicherheit behandeln müssen und dies letztendlich zu einer Leistungseinbuße führen wird. Eine Option zum Überlegen.

5voto

justin.hughey Punkte 1216

Erstellen Sie einen benutzerdefinierten Rückgabetyp, der die überprüfte Ausnahme weitergibt. Dies ist eine Alternative dazu, eine neue Schnittstelle zu erstellen, die die vorhandene funktionale Schnittstelle widerspiegelt, jedoch mit der leichten Modifikation eines "throws exception" für die Methode der funktionalen Schnittstelle.

Definition

CheckedValueSupplier

public static interface CheckedValueSupplier {
    public V get () throws Exception;
}

CheckedValue

public class CheckedValue {
    private final V v;
    private final Optional opt;

    public Value (V v) {
        this.v = v;
    }

    public Value (Exception e) {
        this.opt = Optional.of(e);
    }

    public V get () throws Exception {
        if (opt.isPresent()) {
            throw opt.get();
        }
        return v;
    }

    public Optional getException () {
        return opt;
    }

    public static  CheckedValue returns (T t) {
        return new CheckedValue(t);
    }

    public static  CheckedValue rethrows (Exception e) {
        return new CheckedValue(e);
    }

    public static  CheckedValue from (CheckedValueSupplier sup) {
        try {
            return CheckedValue.returns(sup.get());
        } catch (Exception e) {
            return Result.rethrows(e);
        }
    }

    public static  CheckedValue escalates (CheckedValueSupplier sup) {
        try {
            return CheckedValue.returns(sup.get());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}

Verwendung

// Verwenden Sie dieses Muster nicht mit FileReader, es soll ein Beispiel sein.
// FileReader ist eine Closeable-Ressource und sollte daher in einem try-with-resources-Block oder auf eine andere sichere Weise verwaltet werden, um sicherzustellen, dass sie ordnungsgemäß geschlossen wird.

// Dies wird nicht kompilieren, da der FileReader-Konstruktor eine IOException wirft.
    Function sToFr =
        (fn) -> new FileReader(Paths.get(fn).toFile());

// Alternativ, dies wird kompilieren.
    Function> sToFr = (fn) -> {
        return CheckedValue.from (
            () -> new FileReader(Paths.get("/home/" + f).toFile()));
    };

// Verwendung für einzelne Datensätze
    // Der Aufruf von get() gibt die überprüfte Ausnahme weiter, wenn sie vorhanden ist.
    FileReader readMe = pToFr.apply("/home/README").get();

// Verwendung für eine Liste von Datensätzen
    List paths = ...; //eine Liste von Dateipfaden
    Collection> frs =
        paths.stream().map(pToFr).collect(Collectors.toList());

// Herausfinden, ob die Erstellung eines FileReader fehlgeschlagen ist.
    boolean anyErrors = frs.stream()
        .filter(f -> f.getException().isPresent())
        .findAny().isPresent();

Was passiert hier?

Es wird eine einzelne funktionale Schnittstelle erstellt, die eine überprüfte Ausnahme wirft (CheckedValueSupplier). Dies wird die einzige funktionale Schnittstelle sein, die überprüfte Ausnahmen zulässt. Alle anderen funktionalen Schnittstellen werden den CheckedValueSupplier nutzen, um jeden Code, der eine überprüfte Ausnahme wirft, einzuhüllen.

Die Klasse CheckedValue wird das Ergebnis der Ausführung jedes Logikteils, das eine überprüfte Ausnahme wirft, halten. Dies verhindert das Weiterleiten einer überprüften Ausnahme, bis der Code versucht, auf den Wert zuzugreifen, den eine Instanz von CheckedValue enthält.

Die Probleme dieser Herangehensweise.

  • Wir werfen jetzt "Exception" und verbergen effektiv den ursprünglich geworfenen spezifischen Typ.
  • Wir wissen nicht, dass eine Ausnahme aufgetreten ist, bis CheckedValue#get() aufgerufen wird.

Consumer et al

Einige funktionale Schnittstellen (Consumer zum Beispiel) müssen auf eine andere Weise behandelt werden, da sie keinen Rückgabewert liefern.

Funktion anstelle von Consumer

Ein Ansatz besteht darin, eine Funktion anstelle eines Verbrauchers zu verwenden, was beim Umgang mit Streams zum Tragen kommt.

    List lst = Lists.newArrayList();
// wird nicht kompilieren
lst.stream().forEach(e -> throwyMethod(e));
// kompiliert
lst.stream()
    .map(e -> CheckedValueSupplier.from(
        () -> {throwyMethod(e); return e;}))
    .filter(v -> v.getException().isPresent()); //Dieses Beispiel läuft möglicherweise aufgrund des lazy stream-Verhaltens nicht tatsächlich

Eskalation

Alternativ können Sie immer zu einem RuntimeException eskalieren. Es gibt andere Antworten, die die Eskalation einer überprüften Ausnahme von innerhalb eines Consumers abdecken.

Nicht verbrauchen.

Vermeiden Sie einfach funktionale Schnittstellen ganz und verwenden Sie eine gute alte for-Schleife.

5voto

Advait Purohit Punkte 11

Wenn die Verwendung des Java 8 Function-Interfaces keine Voraussetzung ist, bietet die Apache Commons-Bibliothek seit der Version 3.11 eine alternative FailableFunction-Schnittstelle zur Lösung dieses Problems an - https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/function/FailableFunction.html

So kann es verwendet werden -

Integer myMethod(String s) throws IOException;

FailableFunction function = this::myMethod;

Integer result;
try {
    result = function.apply(s);
} catch (IOException ex) {
    // Behandeln Sie die Ausnahme
}

Überprüfen Sie die vollständige Liste alternativer Failable-Schnittstellen auf der folgenden Seite - https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/function/package-summary.html

5voto

yohan Punkte 103

Ein weiterer Lösungsansatz mit einem Funktionsschalengehäuse wäre, entweder eine Instanz eines Wrapper für Ihr Ergebnis zurückzugeben, sagen wir Success, wenn alles gut gelaufen ist, oder eine Instanz von, sagen wir Failure.

Einige Code, um die Dinge zu klären :

public interface ThrowableFunction {
    B apply(A a) throws Exception;
}

public abstract class Try {

    public static boolean isSuccess(Try tryy) {
        return tryy instanceof Success;
    }

    public static  Function> tryOf(ThrowableFunction function) {
        return a -> {
            try {
                B result = function.apply(a);
                return new Success(result);
            } catch (Exception e) {
                return new Failure<>(e);
            }
        };
    }

    public abstract boolean isSuccess();

    public boolean isError() {
        return !isSuccess();
    }

    public abstract A getResult();

    public abstract Exception getError();
}

public class Success extends Try {

    private final A result;

    public Success(A result) {
        this.result = result;
    }

    @Override
    public boolean isSuccess() {
        return true;
    }

    @Override
    public A getResult() {
        return result;
    }

    @Override
    public Exception getError() {
        return new UnsupportedOperationException();
    }

    @Override
    public boolean equals(Object that) {
        if(!(that instanceof Success)) {
            return false;
        }
        return Objects.equal(result, ((Success) that).getResult());
    }
}

public class Failure extends Try {

    private final Exception exception;

    public Failure(Exception exception) {
        this.exception = exception;
    }

    @Override
    public boolean isSuccess() {
        return false;
    }

    @Override
    public A getResult() {
        throw new UnsupportedOperationException();
    }

    @Override
    public Exception getError() {
        return exception;
    }
}

**Ein einfacher Anwendungsfall :

List> result = Lists.newArrayList(1, 2, 3).stream().
    map(Try.tryOf(i -> someMethodThrowingAnException(i))).
    collect(Collectors.toList());**

5voto

Sergio Punkte 169

Ich hatte dieses Problem mit Class.forName und Class.newInstance innerhalb eines Lambdas, also habe ich einfach Folgendes gemacht:

public Object uncheckedNewInstanceForName (String name) {

    try {
        return Class.forName(name).newInstance();
    }
    catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
        throw new RuntimeException(e);
    }
}

Innerhalb des Lambdas rief ich statt Class.forName("myClass").newInstance() einfach uncheckedNewInstanceForName ("myClass") auf

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X