Java 8: Lambda-Streams, Filter nach Methode mit Ausnahme

168

Ich habe ein Problem beim Ausprobieren der Lambda-Ausdrücke von Java 8. Normalerweise funktioniert es gut, aber jetzt habe ich Methoden, die werfen IOException. Am besten sehen Sie sich den folgenden Code an:

class Bank{
    ....
    public Set<String> getActiveAccountNumbers() throws IOException {
        Stream<Account> s =  accounts.values().stream();
        s = s.filter(a -> a.isActive());
        Stream<String> ss = s.map(a -> a.getNumber());
        return ss.collect(Collectors.toSet());
    }
    ....
}

interface Account{
    ....
    boolean isActive() throws IOException;
    String getNumber() throws IOException;
    ....
}

Das Problem ist, dass es nicht kompiliert wird, da ich die möglichen Ausnahmen der isActive- und der getNumber-Methode abfangen muss. Aber selbst wenn ich explizit einen Try-Catch-Block wie unten verwende, wird er immer noch nicht kompiliert, da ich die Ausnahme nicht abfange. Entweder gibt es einen Fehler in JDK, oder ich weiß nicht, wie ich diese Ausnahmen abfangen soll.

class Bank{
    ....
    //Doesn't compile either
    public Set<String> getActiveAccountNumbers() throws IOException {
        try{
            Stream<Account> s =  accounts.values().stream();
            s = s.filter(a -> a.isActive());
            Stream<String> ss = s.map(a -> a.getNumber());
            return ss.collect(Collectors.toSet());
        }catch(IOException ex){
        }
    }
    ....
}

Wie kann ich es zum Laufen bringen? Kann mich jemand auf die richtige Lösung hinweisen?

Martin Weber
quelle
1
Siehe auch
Vadzim
1
Siehe auch
Marko Topolnik
4
Die einfache und richtige Antwort: Fangen Sie die Ausnahme im Lambda.
Brian Goetz

Antworten:

211

Sie müssen die Ausnahme abfangen, bevor sie dem Lambda entgeht:

s = s.filter(a -> { try { return a.isActive(); } 
                    catch (IOException e) { throw new UncheckedIOException(e); }}});

Bedenken Sie, dass das Lambda nicht an der Stelle ausgewertet wird, an der Sie es schreiben, sondern an einer völlig unabhängigen Stelle innerhalb einer JDK-Klasse. Dies wäre also der Punkt, an dem diese überprüfte Ausnahme ausgelöst würde, und an dieser Stelle wird sie nicht deklariert.

Sie können damit umgehen, indem Sie einen Wrapper Ihres Lambda verwenden, der geprüfte Ausnahmen in ungeprüfte übersetzt:

public static <T> T uncheckCall(Callable<T> callable) {
  try { return callable.call(); }
  catch (RuntimeException e) { throw e; }
  catch (Exception e) { throw new RuntimeException(e); }
}

Ihr Beispiel würde geschrieben werden als

return s.filter(a -> uncheckCall(a::isActive))
        .map(Account::getNumber)
        .collect(toSet());

In meinen Projekten beschäftige ich mich mit diesem Problem, ohne es zu verpacken. Stattdessen verwende ich eine Methode, die die Überprüfung von Ausnahmen durch den Compiler effektiv entschärft. Es ist unnötig zu erwähnen, dass dies mit Vorsicht behandelt werden sollte und jeder im Projekt muss sich bewusst sein, dass eine geprüfte Ausnahme auftreten kann, wenn sie nicht deklariert ist. Dies ist der Installationscode:

public static <T> T uncheckCall(Callable<T> callable) {
  try { return callable.call(); }
  catch (Exception e) { return sneakyThrow(e); }
}
public static void uncheckRun(RunnableExc r) {
  try { r.run(); } catch (Exception e) { sneakyThrow(e); }
}
public interface RunnableExc { void run() throws Exception; }


@SuppressWarnings("unchecked")
private static <T extends Throwable> void sneakyThrow(Throwable t) throws T {
  throw (T) t;
}

und Sie können erwarten, einen IOExceptionWurf in Ihr Gesicht zu bekommen , obwohl collectes nicht deklariert. In den meisten, aber nicht allen realen Fällen möchten Sie die Ausnahme ohnehin nur erneut auslösen und als generischen Fehler behandeln. In all diesen Fällen geht nichts an Klarheit oder Richtigkeit verloren. Achten Sie nur auf die anderen Fälle, in denen Sie tatsächlich sofort auf die Ausnahme reagieren möchten. Der Entwickler wird vom Compiler nicht darauf aufmerksam gemacht, dass es dort einen IOExceptionzu fangenden gibt, und der Compiler wird sich tatsächlich beschweren, wenn Sie versuchen, ihn zu fangen, weil wir ihn getäuscht haben zu glauben, dass keine solche Ausnahme ausgelöst werden kann.

Marko Topolnik
quelle
4
Ich habe NettyIO schon einmal beim "hinterhältigen Wurf" gesehen und wollte meinen Stuhl aus dem Fenster werfen. "Was? Woher kam diese überprüfte Ausnahme?" Dies ist der erste legitime Anwendungsfall für hinterhältige Würfe, den ich bisher gesehen habe. Als Programmierer muss man wachsam sein, wenn man anzeigt, dass ein hinterhältiger Wurf möglich ist. Vielleicht ist es besser, einfach eine andere Stream-Schnittstelle / ein anderes Stream-Impl zu erstellen, das / das die aktivierte Ausnahme unterstützt?
Kevinarpe
8
Keine vernünftige API sollte dem Client nicht deklarierte geprüfte Ausnahmen liefern, das ist sicher. Innerhalb der API kann es jedoch zu einem Verständnis kommen, dass die überprüfte Ausnahme auslaufen kann. Sie verursachen keinen Schaden, solange sie nur ein weiteres Zeichen für ein allgemeines Versagen sind und im Begriff sind, im Großhandel gefangen und gehandhabt zu werden.
Marko Topolnik
5
@ kevinarpe Dies ist der genaue Grund, warum hinterhältige Würfe eine schlechte Idee sind. Das Kurzschließen des Compilers kann zukünftige Betreuer verwirren.
Thorbjørn Ravn Andersen
29
Nur weil Sie die Regeln nicht mögen, heißt das nicht, dass es eine gute Idee ist, das Gesetz selbst in die Hand zu nehmen. Ihr Rat ist unverantwortlich, da der Code-Writer die weitaus wichtigeren Überlegungen zur Transparenz und Wartbarkeit des Programms berücksichtigt.
Brian Goetz
34
@brian Nur weil etwas eine Regel ist, heißt das nicht, dass es eine gute Idee ist. Aber ich bin überrascht, dass Sie den zweiten Teil meiner Antwort als "Rat" bezeichnen, da ich dachte, ich hätte deutlich gemacht, was ich als Lösung vorschlage und was ich dem interessierten Leser als FYI mit reichlichen Haftungsausschlüssen anbiete.
Marko Topolnik
29

Sie können Ihren statischen Schmerz auch mit Lambdas verbreiten, sodass das Ganze lesbar aussieht:

s.filter(a -> propagate(a::isActive))

propagateHier wird java.util.concurrent.Callableals Parameter empfangen und jede während des Aufrufs abgefangene Ausnahme in konvertiert RuntimeException. Es gibt eine ähnliche Konvertierungsmethode Throwables # propagate (Throwable) in Guava.

Diese Methode scheint für die Verkettung von Lambda-Methoden unerlässlich zu sein, daher hoffe ich, dass sie eines Tages zu einer der populären Bibliotheken hinzugefügt wird, oder dass dieses Ausbreitungsverhalten standardmäßig ist.

public class PropagateExceptionsSample {
    // a simplified version of Throwables#propagate
    public static RuntimeException runtime(Throwable e) {
        if (e instanceof RuntimeException) {
            return (RuntimeException)e;
        }

        return new RuntimeException(e);
    }

    // this is a new one, n/a in public libs
    // Callable just suits as a functional interface in JDK throwing Exception 
    public static <V> V propagate(Callable<V> callable){
        try {
            return callable.call();
        } catch (Exception e) {
            throw runtime(e);
        }
    }

    public static void main(String[] args) {
        class Account{
            String name;    
            Account(String name) { this.name = name;}

            public boolean isActive() throws IOException {
                return name.startsWith("a");
            }
        }


        List<Account> accounts = new ArrayList<>(Arrays.asList(new Account("andrey"), new Account("angela"), new Account("pamela")));

        Stream<Account> s = accounts.stream();

        s
          .filter(a -> propagate(a::isActive))
          .map(a -> a.name)
          .forEach(System.out::println);
    }
}
Andrey Chaschev
quelle
22

Mit dieser UtilExceptionHilfsklasse können Sie alle überprüften Ausnahmen in Java-Streams wie folgt verwenden:

Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
      .map(rethrowFunction(Class::forName))
      .collect(Collectors.toList());

Note Class::forNamewirft ClassNotFoundException, was überprüft wird . Der Stream selbst löst ebenfalls aus ClassNotFoundExceptionund NICHT eine nicht aktivierte Ausnahme beim Umschließen.

public final class UtilException {

@FunctionalInterface
public interface Consumer_WithExceptions<T, E extends Exception> {
    void accept(T t) throws E;
    }

@FunctionalInterface
public interface BiConsumer_WithExceptions<T, U, E extends Exception> {
    void accept(T t, U u) throws E;
    }

@FunctionalInterface
public interface Function_WithExceptions<T, R, E extends Exception> {
    R apply(T t) throws E;
    }

@FunctionalInterface
public interface Supplier_WithExceptions<T, E extends Exception> {
    T get() throws E;
    }

@FunctionalInterface
public interface Runnable_WithExceptions<E extends Exception> {
    void run() throws E;
    }

/** .forEach(rethrowConsumer(name -> System.out.println(Class.forName(name)))); or .forEach(rethrowConsumer(ClassNameUtil::println)); */
public static <T, E extends Exception> Consumer<T> rethrowConsumer(Consumer_WithExceptions<T, E> consumer) throws E {
    return t -> {
        try { consumer.accept(t); }
        catch (Exception exception) { throwAsUnchecked(exception); }
        };
    }

public static <T, U, E extends Exception> BiConsumer<T, U> rethrowBiConsumer(BiConsumer_WithExceptions<T, U, E> biConsumer) throws E {
    return (t, u) -> {
        try { biConsumer.accept(t, u); }
        catch (Exception exception) { throwAsUnchecked(exception); }
        };
    }

/** .map(rethrowFunction(name -> Class.forName(name))) or .map(rethrowFunction(Class::forName)) */
public static <T, R, E extends Exception> Function<T, R> rethrowFunction(Function_WithExceptions<T, R, E> function) throws E {
    return t -> {
        try { return function.apply(t); }
        catch (Exception exception) { throwAsUnchecked(exception); return null; }
        };
    }

/** rethrowSupplier(() -> new StringJoiner(new String(new byte[]{77, 97, 114, 107}, "UTF-8"))), */
public static <T, E extends Exception> Supplier<T> rethrowSupplier(Supplier_WithExceptions<T, E> function) throws E {
    return () -> {
        try { return function.get(); }
        catch (Exception exception) { throwAsUnchecked(exception); return null; }
        };
    }

/** uncheck(() -> Class.forName("xxx")); */
public static void uncheck(Runnable_WithExceptions t)
    {
    try { t.run(); }
    catch (Exception exception) { throwAsUnchecked(exception); }
    }

/** uncheck(() -> Class.forName("xxx")); */
public static <R, E extends Exception> R uncheck(Supplier_WithExceptions<R, E> supplier)
    {
    try { return supplier.get(); }
    catch (Exception exception) { throwAsUnchecked(exception); return null; }
    }

/** uncheck(Class::forName, "xxx"); */
public static <T, R, E extends Exception> R uncheck(Function_WithExceptions<T, R, E> function, T t) {
    try { return function.apply(t); }
    catch (Exception exception) { throwAsUnchecked(exception); return null; }
    }

@SuppressWarnings ("unchecked")
private static <E extends Throwable> void throwAsUnchecked(Exception exception) throws E { throw (E)exception; }

}

Viele andere Beispiele zur Verwendung (nach dem statischen Import UtilException):

@Test
public void test_Consumer_with_checked_exceptions() throws IllegalAccessException {
    Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .forEach(rethrowConsumer(className -> System.out.println(Class.forName(className))));

    Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .forEach(rethrowConsumer(System.out::println));
    }

@Test
public void test_Function_with_checked_exceptions() throws ClassNotFoundException {
    List<Class> classes1
          = Stream.of("Object", "Integer", "String")
                  .map(rethrowFunction(className -> Class.forName("java.lang." + className)))
                  .collect(Collectors.toList());

    List<Class> classes2
          = Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
                  .map(rethrowFunction(Class::forName))
                  .collect(Collectors.toList());
    }

@Test
public void test_Supplier_with_checked_exceptions() throws ClassNotFoundException {
    Collector.of(
          rethrowSupplier(() -> new StringJoiner(new String(new byte[]{77, 97, 114, 107}, "UTF-8"))),
          StringJoiner::add, StringJoiner::merge, StringJoiner::toString);
    }

@Test    
public void test_uncheck_exception_thrown_by_method() {
    Class clazz1 = uncheck(() -> Class.forName("java.lang.String"));

    Class clazz2 = uncheck(Class::forName, "java.lang.String");
    }

@Test (expected = ClassNotFoundException.class)
public void test_if_correct_exception_is_still_thrown_by_method() {
    Class clazz3 = uncheck(Class::forName, "INVALID");
    }

Verwenden Sie es jedoch nicht, bevor Sie die folgenden Vor- und Nachteile sowie Einschränkungen verstanden haben :

• Wenn der aufrufende Code die aktivierte Ausnahme behandeln soll, MÜSSEN Sie ihn der throw-Klausel der Methode hinzufügen, die den Stream enthält. Der Compiler zwingt Sie nicht mehr, es hinzuzufügen, daher ist es einfacher, es zu vergessen.

• Wenn der aufrufende Code die aktivierte Ausnahme bereits verarbeitet, erinnert Sie der Compiler daran, die throw-Klausel zur Methodendeklaration hinzuzufügen, die den Stream enthält (wenn Sie dies nicht tun, wird Folgendes angegeben: Die Ausnahme wird niemals im Hauptteil der entsprechenden try-Anweisung ausgelöst ).

• In jedem Fall können Sie den Stream selbst nicht umgeben, um die aktivierte Ausnahme INNERHALB der Methode, die den Stream enthält, abzufangen (wenn Sie es versuchen, sagt der Compiler: Ausnahme wird niemals in den Text der entsprechenden try-Anweisung ausgelöst).

• Wenn Sie eine Methode aufrufen, die buchstäblich nie die von ihr deklarierte Ausnahme auslösen kann, sollten Sie die throw-Klausel nicht einschließen. Beispiel: new String (byteArr, "UTF-8") löst UnsupportedEncodingException aus, aber UTF-8 wird durch die Java-Spezifikation garantiert, dass es immer vorhanden ist. Hier ist die Wurferklärung ein Ärgernis und jede Lösung, um sie mit minimalem Boilerplate zum Schweigen zu bringen, ist willkommen.

• Wenn Sie geprüfte Ausnahmen hassen und der Meinung sind, dass sie niemals der Java-Sprache hinzugefügt werden sollten (eine wachsende Anzahl von Menschen denkt so, und ich bin NICHT einer von ihnen), fügen Sie die geprüfte Ausnahme einfach nicht zu der hinzu löst eine Klausel der Methode aus, die den Stream enthält. Die aktivierte Ausnahme verhält sich dann wie eine nicht aktivierte Ausnahme.

• Wenn Sie eine strikte Schnittstelle implementieren, in der Sie nicht die Möglichkeit haben, eine Throws-Deklaration hinzuzufügen, und dennoch das Auslösen einer Ausnahme völlig angemessen ist, führt das Umschließen einer Ausnahme, um das Privileg zu erhalten, sie auszulösen, zu einer Stapelverfolgung mit falschen Ausnahmen die keine Informationen darüber beitragen, was tatsächlich schief gelaufen ist. Ein gutes Beispiel ist Runnable.run (), das keine überprüften Ausnahmen auslöst. In diesem Fall können Sie entscheiden, die aktivierte Ausnahme nicht zur throw-Klausel der Methode hinzuzufügen, die den Stream enthält.

• Wenn Sie die aktivierte Ausnahme NICHT zur Throws-Klausel der Methode, die den Stream enthält, hinzufügen (oder vergessen, sie hinzuzufügen), sollten Sie sich in jedem Fall dieser beiden Konsequenzen bewusst sein, wenn Sie CHECKED-Ausnahmen auslösen:

1) Der aufrufende Code kann ihn nicht beim Namen abfangen (wenn Sie es versuchen, sagt der Compiler: Eine Ausnahme wird niemals in den Text der entsprechenden try-Anweisung geworfen). Es sprudelt und wird wahrscheinlich in der Hauptprogrammschleife von einer "catch Exception" oder "catch Throwable" abgefangen, was Sie vielleicht sowieso wollen.

2) Es verstößt gegen das Prinzip der geringsten Überraschung: Es reicht nicht mehr aus, RuntimeException abzufangen, um das Abfangen aller möglichen Ausnahmen garantieren zu können. Aus diesem Grund glaube ich, dass dies nicht im Framework-Code erfolgen sollte, sondern nur im Business-Code, den Sie vollständig kontrollieren.

Fazit: Ich glaube, die Einschränkungen hier sind nicht ernst und die UtilExceptionKlasse kann ohne Angst benutzt werden. Es liegt jedoch an Ihnen!

MarcG
quelle
8

Sie können möglicherweise Ihre eigenen rollen Stream Variante indem Sie Ihr Lambda umbrechen, um eine ungeprüfte Ausnahme auszulösen, und diese ungeprüfte Ausnahme später bei Terminaloperationen auspacken:

@FunctionalInterface
public interface ThrowingPredicate<T, X extends Throwable> {
    public boolean test(T t) throws X;
}

@FunctionalInterface
public interface ThrowingFunction<T, R, X extends Throwable> {
    public R apply(T t) throws X;
}

@FunctionalInterface
public interface ThrowingSupplier<R, X extends Throwable> {
    public R get() throws X;
}

public interface ThrowingStream<T, X extends Throwable> {
    public ThrowingStream<T, X> filter(
            ThrowingPredicate<? super T, ? extends X> predicate);

    public <R> ThrowingStream<T, R> map(
            ThrowingFunction<? super T, ? extends R, ? extends X> mapper);

    public <A, R> R collect(Collector<? super T, A, R> collector) throws X;

    // etc
}

class StreamAdapter<T, X extends Throwable> implements ThrowingStream<T, X> {
    private static class AdapterException extends RuntimeException {
        public AdapterException(Throwable cause) {
            super(cause);
        }
    }

    private final Stream<T> delegate;
    private final Class<X> x;

    StreamAdapter(Stream<T> delegate, Class<X> x) {
        this.delegate = delegate;
        this.x = x;
    }

    private <R> R maskException(ThrowingSupplier<R, X> method) {
        try {
            return method.get();
        } catch (Throwable t) {
            if (x.isInstance(t)) {
                throw new AdapterException(t);
            } else {
                throw t;
            }
        }
    }

    @Override
    public ThrowingStream<T, X> filter(ThrowingPredicate<T, X> predicate) {
        return new StreamAdapter<>(
                delegate.filter(t -> maskException(() -> predicate.test(t))), x);
    }

    @Override
    public <R> ThrowingStream<R, X> map(ThrowingFunction<T, R, X> mapper) {
        return new StreamAdapter<>(
                delegate.map(t -> maskException(() -> mapper.apply(t))), x);
    }

    private <R> R unmaskException(Supplier<R> method) throws X {
        try {
            return method.get();
        } catch (AdapterException e) {
            throw x.cast(e.getCause());
        }
    }

    @Override
    public <A, R> R collect(Collector<T, A, R> collector) throws X {
        return unmaskException(() -> delegate.collect(collector));
    }
}

Dann könnten Sie dies genauso verwenden wie a Stream :

Stream<Account> s = accounts.values().stream();
ThrowingStream<Account, IOException> ts = new StreamAdapter<>(s, IOException.class);
return ts.filter(Account::isActive).map(Account::getNumber).collect(toSet());

Diese Lösung würde einiges an Boilerplate erfordern, daher schlage ich vor, dass Sie sich die Bibliothek ansehen, die ich bereits erstellt habe und die genau das tut, was ich hier für die gesamte StreamKlasse beschrieben habe (und mehr!).

Jeffrey
quelle
Hallo ... kleiner Fehler? new StreamBridge<>(ts, IOException.class);->new StreamBridge<>(s, IOException.class);
Kevinarpe
1
@ kevinarpe Ja. Es hätte auch sagen sollen StreamAdapter.
Jeffrey
5

Verwenden Sie die Methode #propagate (). Beispiel für eine Nicht-Guava-Implementierung von Java 8-Blog von Sam Beran :

public class Throwables {
    public interface ExceptionWrapper<E> {
        E wrap(Exception e);
    }

    public static <T> T propagate(Callable<T> callable) throws RuntimeException {
        return propagate(callable, RuntimeException::new);
    }

    public static <T, E extends Throwable> T propagate(Callable<T> callable, ExceptionWrapper<E> wrapper) throws E {
        try {
            return callable.call();
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw wrapper.wrap(e);
        }
    }
}
n0mer
quelle
Der Java 8-Blog-Link ist tot.
Spycho
4

Dies beantwortet die Frage nicht direkt (es gibt viele andere Antworten, die dies tun), sondern versucht zunächst, das Problem zu vermeiden:

Nach meiner Erfahrung Streamergibt sich die Notwendigkeit, Ausnahmen in einem (oder einem anderen Lambda-Ausdruck) zu behandeln, häufig aus der Tatsache, dass die Ausnahmen als von Methoden ausgelöst deklariert werden, bei denen sie nicht ausgelöst werden sollten. Dies ist häufig auf das Mischen von Geschäftslogik mit In- und Output zurückzuführen. Ihre AccountBenutzeroberfläche ist ein perfektes Beispiel:

interface Account {
    boolean isActive() throws IOException;
    String getNumber() throws IOException;
}

IOExceptionBetrachten Sie dieses Design, anstatt auf jeden Getter einen zu werfen :

interface AccountReader {
    Account readAccount(…) throws IOException;
}

interface Account {
    boolean isActive();
    String getNumber();
}

Die Methode AccountReader.readAccount(…)könnte ein Konto aus einer Datenbank oder einer Datei oder was auch immer lesen und eine Ausnahme auslösen, wenn dies nicht erfolgreich ist. Es wird ein AccountObjekt erstellt, das bereits alle Werte enthält und zur Verwendung bereit ist. Da die Werte bereits von geladen wurden readAccount(…), würden die Getter keine Ausnahme auslösen. So können Sie sie frei in Lambdas verwenden, ohne die Ausnahmen einwickeln, maskieren oder ausblenden zu müssen.

Natürlich ist es nicht immer möglich, es so zu machen, wie ich es beschrieben habe, aber oft ist es das und es führt insgesamt zu saubererem Code (IMHO):

  • Bessere Trennung von Bedenken und Befolgung des Grundsatzes der Einzelverantwortung
  • Weniger Boilerplate: Sie müssen Ihren Code nicht überladen throws IOException nicht ohne Verwendung überladen, sondern müssen den Compiler zufriedenstellen
  • Fehlerbehandlung: Sie behandeln die Fehler dort, wo sie auftreten - beim Lesen aus einer Datei oder Datenbank - anstatt irgendwo in der Mitte Ihrer Geschäftslogik, nur weil Sie einen Feldwert erhalten möchten
  • Sie können möglicherweise unveränderlich machenAccount und von den Vorteilen profitieren (z. B. Gewindesicherheit).
  • Sie benötigen keine "schmutzigen Tricks" oder Problemumgehungen, um sie Accountin Lambdas zu verwenden (z. B. in a Stream).
siegi
quelle
4

Es kann durch folgenden einfachen Code mit Stream und Try in AbacusUtil gelöst werden :

Stream.of(accounts).filter(a -> Try.call(a::isActive)).map(a -> Try.call(a::getNumber)).toSet();

Offenlegung: Ich bin der Entwickler von AbacusUtil.

user_3380739
quelle
3

Erweitern @marcg Lösung können Sie in der Regel eine werfen und fangen geprüft Ausnahme in Bächen; Das heißt, der Compiler wird Sie bitten, zu fangen / erneut zu werfen, so wie Sie sich außerhalb von Streams befanden !!

@FunctionalInterface
public interface Predicate_WithExceptions<T, E extends Exception> {
    boolean test(T t) throws E;
}

/**
 * .filter(rethrowPredicate(t -> t.isActive()))
 */
public static <T, E extends Exception> Predicate<T> rethrowPredicate(Predicate_WithExceptions<T, E> predicate) throws E {
    return t -> {
        try {
            return predicate.test(t);
        } catch (Exception exception) {
            return throwActualException(exception);
        }
    };
}

@SuppressWarnings("unchecked")
private static <T, E extends Exception> T throwActualException(Exception exception) throws E {
    throw (E) exception;
}

Dann würde Ihr Beispiel wie folgt geschrieben werden (Hinzufügen von Tests, um es klarer zu zeigen):

@Test
public void testPredicate() throws MyTestException {
    List<String> nonEmptyStrings = Stream.of("ciao", "")
            .filter(rethrowPredicate(s -> notEmpty(s)))
            .collect(toList());
    assertEquals(1, nonEmptyStrings.size());
    assertEquals("ciao", nonEmptyStrings.get(0));
}

private class MyTestException extends Exception { }

private boolean notEmpty(String value) throws MyTestException {
    if(value==null) {
        throw new MyTestException();
    }
    return !value.isEmpty();
}

@Test
public void testPredicateRaisingException() throws MyTestException {
    try {
        Stream.of("ciao", null)
                .filter(rethrowPredicate(s -> notEmpty(s)))
                .collect(toList());
        fail();
    } catch (MyTestException e) {
        //OK
    }
}
PaoloC
quelle
Dieses Beispiel wird nicht kompiliert
Roman M
Hallo @RomanM, danke, dass du darauf hingewiesen hast: Ich habe den fehlenden Rückgabetyp für die Methode "throwActualException" korrigiert. Wir verwenden dies in der Produktion, also hoffe ich, dass es auch auf Ihrer Seite funktioniert.
PaoloC
3

Um den IOException-Verarbeitungscode (zu RuntimeException) ordnungsgemäß hinzuzufügen, sieht Ihre Methode folgendermaßen aus:

Stream<Account> s =  accounts.values().stream();

s = s.filter(a -> { try { return a.isActive(); } 
  catch (IOException e) { throw new RuntimeException(e); }});

Stream<String> ss = s.map(a -> { try { return a.getNumber() }
  catch (IOException e) { throw new RuntimeException(e); }});

return ss.collect(Collectors.toSet());

Das Problem ist nun, dass das IOExceptionals erfasst RuntimeExceptionund wieder in ein konvertiert werden mussIOException - und dass dadurch der obigen Methode noch mehr Code hinzugefügt wird.

Warum verwenden, Streamwenn es einfach so gemacht werden kann - und die Methode wirft, IOExceptionso dass auch dafür kein zusätzlicher Code benötigt wird:

Set<String> set = new HashSet<>();
for(Account a: accounts.values()){
  if(a.isActive()){
     set.add(a.getNumber());
  } 
}
return set;
Der Koordinator
quelle
1

Unter Berücksichtigung dieses Problems habe ich eine kleine Bibliothek für den Umgang mit geprüften Ausnahmen und Lambdas entwickelt. Mit benutzerdefinierten Adaptern können Sie vorhandene Funktionstypen integrieren:

stream().map(unchecked(URI::new)) //with a static import

https://github.com/TouK/ThrowingFunction/

Grzegorz Piwowarek
quelle
1

Ihr Beispiel kann wie folgt geschrieben werden:

import utils.stream.Unthrow;

class Bank{
   ....
   public Set<String> getActiveAccountNumbers() {
       return accounts.values().stream()
           .filter(a -> Unthrow.wrap(() -> a.isActive()))
           .map(a -> Unthrow.wrap(() -> a.getNumber()))
           .collect(Collectors.toSet());
   }
   ....
}

Die Unthrow- Klasse kann hier https://github.com/SeregaLBN/StreamUnthrower genommen werden

SeregaLBN
quelle
0

Wenn es Ihnen nichts ausmacht, Bibliotheken von Drittanbietern zu verwenden, verfügt AOLs Cyclops-React Lib, Disclosure :: Ich bin ein Mitwirkender, über eine ExceptionSoftener- Klasse, die hier helfen kann.

 s.filter(softenPredicate(a->a.isActive()));
John McClean
quelle
0

Die Funktionsschnittstellen in Java deklarieren keine aktivierte oder deaktivierte Ausnahme. Wir müssen die Signatur der Methoden ändern von:

boolean isActive() throws IOException; 
String getNumber() throwsIOException;

Zu:

boolean isActive();
String getNumber();

Oder behandeln Sie es mit Try-Catch-Block:

public Set<String> getActiveAccountNumbers() {
  Stream<Account> s =  accounts.values().stream();
  s = s.filter(a -> 
    try{
      a.isActive();
    }catch(IOException e){
      throw new RuntimeException(e);
    }
  );
  Stream<String> ss = s.map(a -> 
    try{
      a.getNumber();
    }catch(IOException e){
      throw new RuntimeException(e);
    }
  );
  return ss.collect(Collectors.toSet());
}

Eine andere Möglichkeit besteht darin, einen benutzerdefinierten Wrapper zu schreiben oder eine Bibliothek wie ThrowingFunction zu verwenden. Mit der Bibliothek müssen wir nur die Abhängigkeit zu unserer pom.xml hinzufügen:

<dependency>
    <groupId>pl.touk</groupId>
    <artifactId>throwing-function</artifactId>
    <version>1.3</version>
</dependency>

Und verwenden Sie die spezifischen Klassen wie ThrowingFunction, ThrowingConsumer, ThrowingPredicate, ThrowingRunnable, ThrowingSupplier.

Am Ende sieht der Code folgendermaßen aus:

public Set<String> getActiveAccountNumbers() {
  return accounts.values().stream()
    .filter(ThrowingPredicate.unchecked(Account::isActive))
    .map(ThrowingFunction.unchecked(Account::getNumber))
    .collect(Collectors.toSet());
}
SHoko
quelle
0

Ich sehe keine Möglichkeit, geprüfte Ausnahmen im Stream (Java -8) zu behandeln. Die einzige Möglichkeit, die ich angewendet habe, besteht darin, geprüfte Ausnahmen im Stream abzufangen und sie als ungeprüfte Ausnahme erneut auszulösen.

        Arrays.stream(VERSIONS)
        .map(version -> TemplateStore.class
                .getClassLoader().getResourceAsStream(String.format(TEMPLATE_FILE_MASK, version)))
        .map(inputStream -> {
            try {
                return ((EdiTemplates) JAXBContext.newInstance(EdiTemplates.class).createUnmarshaller()
                        .unmarshal(inputStream)).getMessageTemplate();
            } catch (JAXBException e) {
                throw new IllegalArgumentException(ERROR, e);
            }})
        .flatMap(Collection::stream)
        .collect(Collectors.toList());
atul sachan
quelle
Versuchen Sie tatsächlich, die Frage zu beantworten?
Nilambar Sharma
@Nilambar - Ich versuche hier zu sagen, dass es keine Möglichkeit gibt, die aktivierte Ausnahme zu behandeln, während Java Stream verwendet wird. Am Ende von allem, was wir brauchen, um die aktivierte zu fangen und die Laufzeit / ungeprüfte zu werfen. Jetzt Es gibt zwei Dinge: 1. Wenn Sie der Meinung sind, dass mein Verständnis nicht korrekt ist, korrigieren Sie mich bitte. 2. Wenn Sie der Meinung sind, dass mein Beitrag irrelevant ist, entferne ich ihn gerne. Grüße, Atul
atul sachan
0

Wenn Sie die Ausnahme innerhalb des Streams behandeln und die zusätzlichen weiter verarbeiten möchten, gibt es in DZone einen ausgezeichneten Artikel von Brian Vermeer, der das Konzept eines Entweder verwendet. Es zeigt eine hervorragende Möglichkeit, mit dieser Situation umzugehen. Das einzige, was fehlt, ist Beispielcode. Dies ist ein Beispiel meiner Erkundung unter Verwendung der Konzepte aus diesem Artikel.

@Test
public void whenValuePrinted_thenPrintValue() {

    List<Integer> intStream = Arrays.asList(0, 1, 2, 3, 4, 5, 6);
    intStream.stream().map(Either.liftWithValue(item -> doSomething(item)))
             .map(item -> item.isLeft() ? item.getLeft() : item.getRight())
             .flatMap(o -> {
                 System.out.println(o);
                 return o.isPresent() ? Stream.of(o.get()) : Stream.empty();
             })
             .forEach(System.out::println);
}

private Object doSomething(Integer item) throws Exception {

    if (item == 0) {
        throw new Exception("Zero ain't a number!");
    } else if (item == 4) {
        return Optional.empty();
    }

    return item;
}
Steve Gelman
quelle