Jak mogę wyrzucać zaznaczone wyjątki ze strumieni Java 8?

Jak mogę wyrzucić zaznaczone wyjątki z wewnątrz Java 8 streams / lambda?

Innymi słowy, chcę stworzyć taki kod kompilacji:

public List<Class> getClasses() throws ClassNotFoundException {     

    List<Class> classes = 
        Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
              .map(className -> Class.forName(className))
              .collect(Collectors.toList());                  
    return classes;
    }

Ten kod nie jest kompilowany, ponieważ metoda Class.forName() powyżej rzuca ClassNotFoundException, która jest sprawdzana.

Proszę zauważyć, że nie chcę zawijać zaznaczonego wyjątku wewnątrz wyjątku uruchomieniowego i zamiast tego rzucać zawinięty, niezaznaczony wyjątek. chcę rzucić sam zaznaczony wyjątek i bez dodawania brzydkich try/catches na strumień.

Author: Draken, 2014-12-25

15 answers

Prosta odpowiedź na twoje pytanie brzmi: nie możesz, przynajmniej nie bezpośrednio.I to nie twoja wina. / Align = "left" / trzymają się pojęcia sprawdzonych wyjątków, ale niekonsekwentnie zapomnieli o sprawdzonych wyjątkach przy projektowaniu funkcjonalnych interfejsów, strumieni, lambda itp. To wszystko dla takich ekspertów jak Robert C. Martin, którzy nazywają sprawdzone wyjątki nieudanym eksperymentem.

To rzeczywiście jest ogromny błąd w API i drobny błąd w specyfikacji języka .

Błąd w API polega na tym, że nie zapewnia on możliwości przekazywania sprawdzonych WYJĄTKÓW, gdzie w rzeczywistości miałoby to ogromny sens dla programowania funkcyjnego. Jak zademonstruję poniżej, taki obiekt byłby łatwo możliwy.

Błąd w specyfikacji języka polega na tym, że parametr type nie pozwala na wnioskowanie listy typów zamiast pojedynczego typu tak długo, jak typ parametr jest używany tylko w sytuacjach, w których lista typów jest dopuszczalna (klauzulathrows).

Jako programiści Javy oczekujemy, że następujący kod powinien się skompilować:

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class CheckedStream {
    // List variant to demonstrate what we actually had before refactoring.
    public List<Class> getClasses(final List<String> names) throws ClassNotFoundException {
        final List<Class> classes = new ArrayList<>();
        for (final String name : names)
            classes.add(Class.forName(name));
        return classes;
    }

    // The Stream function which we want to compile.
    public Stream<Class> getClasses(final Stream<String> names) throws ClassNotFoundException {
        return names.map(Class::forName);
    }
}

Daje jednak:

cher@armor1:~/playground/Java/checkedStream$ javac CheckedStream.java 
CheckedStream.java:13: error: incompatible thrown types ClassNotFoundException in method reference
        return names.map(Class::forName);
                         ^
1 error

Sposób definiowania interfejsów funkcyjnych obecnie uniemożliwia kompilatorowi przekazanie wyjątku - nie ma deklaracji, która mówiłaby Stream.map(), że jeśli Function.apply() throws E, Stream.map() throws E też.

Brakuje deklaracji typu parametr do przekazywania sprawdzonych WYJĄTKÓW. Poniższy kod pokazuje, w jaki sposób taki parametr typu pass-through mógł zostać zadeklarowany przy bieżącej składni. Z wyjątkiem specjalnego przypadku w zaznaczonej linii, który jest limitem omówionym poniżej, kod ten kompiluje i zachowuje się zgodnie z oczekiwaniami.

import java.io.IOException;
interface Function<T, R, E extends Throwable> {
    // Declare you throw E, whatever that is.
    R apply(T t) throws E;
}   

interface Stream<T> {
    // Pass through E, whatever mapper defined for E.
    <R, E extends Throwable> Stream<R> map(Function<? super T, ? extends R, E> mapper) throws E;
}   

class Main {
    public static void main(final String... args) throws ClassNotFoundException {
        final Stream<String> s = null;

        // Works: E is ClassNotFoundException.
        s.map(Class::forName);

        // Works: E is RuntimeException (probably).
        s.map(Main::convertClass);

        // Works: E is ClassNotFoundException.
        s.map(Main::throwSome);

        // Doesn't work: E is Exception.
        s.map(Main::throwSomeMore);  // error: unreported exception Exception; must be caught or declared to be thrown
    }   

    public static Class convertClass(final String s) {
        return Main.class;
    }   

    static class FooException extends ClassNotFoundException {}

    static class BarException extends ClassNotFoundException {}

    public static Class throwSome(final String s) throws FooException, BarException {
        throw new FooException();
    }   

    public static Class throwSomeMore(final String s) throws ClassNotFoundException, IOException  {
        throw new FooException();
    }   
}   

W przypadku throwSomeMore chcielibyśmy, aby IOException było chybione, ale tak naprawdę chybione Exception.

To nie jest idealne, ponieważ wnioskowanie typu wydaje się szukać pojedynczy typ, nawet w przypadku WYJĄTKÓW. Ponieważ wnioskowanie typu wymaga pojedynczego typu, E musi rozstrzygnąć do wspólnego super z ClassNotFoundException i IOException, CZYLI Exception.

Konieczne jest dostosowanie definicji wnioskowania typu, aby kompilator szukał wielu typów, jeśli parametr typu jest używany, gdy dopuszczalna jest lista typów (klauzulathrows). Wtedy typ wyjątku zgłaszany przez kompilator będzie tak specyficzny jak oryginalna deklaracja throws sprawdzonego wyjątki metody odniesienia, ani jednego typu super catch-all.

Zła wiadomość jest taka, że to oznacza, że Oracle to spieprzyło. Z pewnością nie złamie kodu user-land, ale wprowadzenie parametrów typu wyjątku do istniejących interfejsów funkcjonalnych złamie kompilację całego kodu user-land, który używa tych interfejsów jawnie. Będą musieli wymyślić jakiś nowy cukier składniowy, żeby to naprawić. [17]} jeszcze gorszą wiadomością jest to, że Temat ten był już omawiany przez Briana Goetza w 2010 https://blogs.oracle.com/briangoetz/entry/exception_transparency_in_java i wydaje się, że ten problem został po prostu zignorowany, więc zastanawiam się, co robi Oracle.
 206
Author: Christian Hujer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-27 14:49:59

Ta klasa pomocnicza LambdaExceptionUtil pozwala na użycie dowolnych zaznaczonych WYJĄTKÓW w strumieniach Javy, takich jak:

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

Uwaga Class::forName rzuca ClassNotFoundException, która jest sprawdzona . Sam strumień również rzuca ClassNotFoundException, a nie jakiś wyjątek zawijania.

public final class LambdaExceptionUtil {

@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; }

}

Wiele innych przykładów jak go używać (po zaimportowaniu statycznie LambdaExceptionUtil):

@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");
    }    

Uwaga 1: metody rethrow klasy LambdaExceptionUtil mogą być używane bez obaw i są W porządku do użycia w dowolnym situation . Wielkie podziękowania dla użytkownika @ PaoloC, który pomógł rozwiązać ostatni problem: teraz kompilator poprosi Cię o dodanie klauzul throw i wszystko jest tak, jakbyś mógł natywnie rzucać zaznaczone wyjątki na strumieniach Java 8.


Uwaga 2: metody uncheck klasy powyżej są metodami bonusowymi i mogą być bezpiecznie usunięte z klasy, jeśli nie chcesz ich używać. Jeśli używasz ich, zrób to ostrożnie, a nie przed zrozumieniem następujących przypadków użycia, zalety/wady i ograniczenia:

* możesz użyć metod uncheck, jeśli wywołujesz metodę, która dosłownie nigdy nie może wyrzucić deklarowanego wyjątku. Na przykład: new String (byteArr, "UTF-8") wyrzuca Nieobsługiwanencodingexception, ale specyfikacja Java gwarantuje, że UTF-8 zawsze będzie obecny. W tym przypadku deklaracja rzutów jest uciążliwa i mile widziane jest każde rozwiązanie, aby wyciszyć ją minimalnym kotłem: String text = uncheck(() -> new String(byteArr, "UTF-8"));

• możesz użyć metod uncheck, jeśli jesteś zaimplementowanie ścisłego interfejsu, w którym nie ma opcji dodawania deklaracji throws, a jednak rzucenie wyjątku jest całkowicie właściwe. Owijanie wyjątku tylko po to, aby zyskać przywilej rzucania go, powoduje stacktrace z fałszywymi wyjątkami, które nie dostarczają informacji o tym, co faktycznie poszło źle. Dobrym przykładem jest Runnable.run (), która nie wyrzuca żadnych zaznaczonych WYJĄTKÓW.

• w każdym przypadku, jeśli zdecydujesz się użyć metod uncheck , bądź świadomy tych 2 konsekwencje wyrzucenia zaznaczonych WYJĄTKÓW bez klauzuli throws: 1) kod wywołujący nie będzie w stanie złapać go po nazwie (jeśli spróbujesz, kompilator powie: wyjątek nigdy nie jest wrzucany do odpowiedniej instrukcji try). Będzie bańka i prawdopodobnie zostanie złapany w głównej pętli programu przez jakiś "catch Exception" lub "catch Throwable" , co może być tym, czego chcesz. 2) łamie zasadę najmniejszego zaskoczenia: nie wystarczy już złapać RuntimeException, aby móc zagwarantować połów wszystkie możliwe wyjątki. Z tego powodu uważam, że nie powinno się tego robić w kodach ramowych, ale tylko w kodach biznesowych, które całkowicie kontrolujesz.

 146
Author: MarcG,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-26 19:09:44

Nie możesz tego zrobić bezpiecznie. Możesz oszukiwać, ale wtedy twój program jest zepsuty i to nieuchronnie wróci, aby ugryźć kogoś (to powinieneś być ty, ale często nasze oszustwo wybucha na kogoś innego.)

Tutaj jest nieco bezpieczniejszy sposób, aby to zrobić (ale nadal nie polecam tego.)

class WrappedException extends RuntimeException {
    Throwable cause;

    WrappedException(Throwable cause) { this.cause = cause; }
}

static WrappedException throwWrapped(Throwable t) {
    throw new WrappedException(t);
}

try 
    source.stream()
          .filter(e -> { ... try { ... } catch (IOException e) { throwWrapped(e); } ... })
          ...
}
catch (WrappedException w) {
    throw (IOException) w.cause;
}

Tutaj, to co robisz, to wyłapywanie wyjątku w lambda, wyrzucanie sygnału z rurociągu strumienia, który wskazuje, że obliczenia nie powiodły się wyjątkowo, wyłapywanie sygnału i działając na ten sygnał, aby rzucić podstawowy wyjątek. Kluczem jest to, że zawsze łapiesz syntetyczny wyjątek, zamiast zezwalać na wyciek sprawdzonego wyjątku bez deklarowania, że wyjątek został wyrzucony.

 21
Author: Brian Goetz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-24 12:28:57

Możesz!

Rozszerzenie @marcg 's UtilException i dodanie throw Etam, gdzie jest to konieczne: w ten sposób kompilator poprosi Cię o dodanie klauzul throwi wszystko jest tak, jakbyś mógł rzucić zaznaczone wyjątki natywnie w strumieniach java 8.

Instrukcje: po prostu skopiuj / wklej LambdaExceptionUtil do swojego IDE, a następnie użyj go, jak pokazano poniżej LambdaExceptionUtilTest.

public final class LambdaExceptionUtil {

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

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

    /**
     * .forEach(rethrowConsumer(name -> System.out.println(Class.forName(name))));
     */
    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) {
                throwActualException(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) {
                throwActualException(exception);
                return null;
            }
        };
    }

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

}

Jakiś test pokazujący użycie i zachowanie:

public class LambdaExceptionUtilTest {

    @Test(expected = MyTestException.class)
    public void testConsumer() throws MyTestException {
        Stream.of((String)null).forEach(rethrowConsumer(s -> checkValue(s)));
    }

    private void checkValue(String value) throws MyTestException {
        if(value==null) {
            throw new MyTestException();
        }
    }

    private class MyTestException extends Exception { }

    @Test
    public void testConsumerRaisingExceptionInTheMiddle() {
        MyLongAccumulator accumulator = new MyLongAccumulator();
        try {
            Stream.of(2L, 3L, 4L, null, 5L).forEach(rethrowConsumer(s -> accumulator.add(s)));
            fail();
        } catch (MyTestException e) {
            assertEquals(9L, accumulator.acc);
        }
    }

    private class MyLongAccumulator {
        private long acc = 0;
        public void add(Long value) throws MyTestException {
            if(value==null) {
                throw new MyTestException();
            }
            acc += value;
        }
    }

    @Test
    public void testFunction() throws MyTestException {
        List<Integer> sizes = Stream.of("ciao", "hello").<Integer>map(rethrowFunction(s -> transform(s))).collect(toList());
        assertEquals(2, sizes.size());
        assertEquals(4, sizes.get(0).intValue());
        assertEquals(5, sizes.get(1).intValue());
    }

    private Integer transform(String value) throws MyTestException {
        if(value==null) {
            throw new MyTestException();
        }
        return value.length();
    }

    @Test(expected = MyTestException.class)
    public void testFunctionRaisingException() throws MyTestException {
        Stream.of("ciao", null, "hello").<Integer>map(rethrowFunction(s -> transform(s))).collect(toList());
    }

}
 21
Author: PaoloC,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-03 10:22:14

Po prostu użyj jednego z NoException (Mój projekt), jooooo, rzucanie-lambda, Throwable interfaces , or Faux Pas .

// NoException
stream.map(Exceptions.sneak().function(Class::forName));

// jOOλ
stream.map(Unchecked.function(Class::forName));

// throwing-lambdas
stream.map(Throwing.function(Class::forName).sneakyThrow());

// Throwable interfaces
stream.map(FunctionWithThrowable.aFunctionThatUnsafelyThrowsUnchecked(Class::forName));

// Faux Pas
stream.map(FauxPas.throwingFunction(Class::forName));
 9
Author: Robert Važan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-16 03:44:51

Napisałem bibliotekę , która rozszerza API strumienia, aby umożliwić rzucanie zaznaczonych WYJĄTKÓW. Wykorzystuje sztuczkę Briana Goetza.

Twój kod stałby się

public List<Class> getClasses() throws ClassNotFoundException {     
    Stream<String> classNames = 
        Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String");

    return ThrowingStream.of(classNames, ClassNotFoundException.class)
               .map(Class::forName)
               .collect(Collectors.toList());
}
 6
Author: Jeffrey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-10 16:49:37

Ta odpowiedź jest podobna do 17, ale unikając definicji wyjątku wrappera:

List test = new ArrayList();
        try {
            test.forEach(obj -> {

                //let say some functionality throws an exception
                try {
                    throw new IOException("test");
                }
                catch(Exception e) {
                    throw new RuntimeException(e);
                }
            });
        }
        catch (RuntimeException re) {
            if(re.getCause() instanceof IOException) {
                //do your logic for catching checked
            }
            else 
                throw re; // it might be that there is real runtime exception
        }
 6
Author: Radoslav Stoyanov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-01 15:09:22

Nie możesz.

Warto jednak rzucić okiem na jeden z moich projektów, który pozwala łatwiej manipulować takimi "rzucającymi lambdami".

W Twoim przypadku, byłabyś w stanie to zrobić:

import static com.github.fge.lambdas.functions.Functions.wrap;

final ThrowingFunction<String, Class<?>> f = wrap(Class::forName);

List<Class> classes =
    Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .map(f.orThrow(MyException.class))
          .collect(Collectors.toList());

I złapać MyException.

To jeden z przykładów. Innym przykładem jest to, że można .orReturn() jakąś wartość domyślną.

Zauważ, że jest to nadal praca w toku, więcej ma nadejść. Lepsze nazwy, więcej funkcji itp.

 5
Author: fge,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-27 15:27:10

Podsumowując powyższe komentarze, zaawansowanym rozwiązaniem jest użycie specjalnego opakowania dla niezaznaczonych funkcji z budowniczym, takim jak API, które zapewnia odzyskiwanie, ponowne przeglądanie i tłumienie.

Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .map(Try.<String, Class<?>>safe(Class::forName)
                  .handle(System.out::println)
                  .unsafe())
          .collect(toList());

Poniższy kod pokazuje to dla interfejsów konsumentów, dostawców i funkcji. Można go łatwo rozszerzyć. Niektóre publiczne słowa kluczowe zostały usunięte w tym przykładzie.

Class Try jest punktem końcowym kodu klienta. Bezpieczne metody mogą mieć unikalną nazwę dla każdej funkcji Typ. CheckedConsumer, CheckedSupplier i CheckedFunction są sprawdzonymi analogami funkcji lib, które mogą być używane niezależnie od Try

CheckedBuilder {[8] } jest interfejsem do obsługi wyjątków w niektórych funkcji checked. orTry pozwala na wykonanie innej funkcji tego samego typu, jeśli poprzednia nie powiodła się. handle zapewnia obsługę wyjątków, w tym filtrowanie typów wyjątków. Kolejność obsługi jest ważna. Redukcja metod unsafe i rethrow rethrows ostatni wyjątek w łańcuchu realizacji. Metody Reduce orElse i orElseGet zwracają alternatywną wartość, jak opcjonalne, jeśli wszystkie funkcje zawiodły. Istnieje również metoda tłumić . CheckedWrapper jest powszechną implementacją CheckedBuilder.

final class Try {

    public static <T> CheckedBuilder<Supplier<T>, CheckedSupplier<T>, T> 
        safe(CheckedSupplier<T> supplier) {
        return new CheckedWrapper<>(supplier, 
                (current, next, handler, orResult) -> () -> {
            try { return current.get(); } catch (Exception ex) {
                handler.accept(ex);
                return next.isPresent() ? next.get().get() : orResult.apply(ex);
            }
        });
    }

    public static <T> Supplier<T> unsafe(CheckedSupplier<T> supplier) {
        return supplier;
    }

    public static <T> CheckedBuilder<Consumer<T>, CheckedConsumer<T>, Void> 
        safe(CheckedConsumer<T> consumer) {
        return new CheckedWrapper<>(consumer, 
                (current, next, handler, orResult) -> t -> {
            try { current.accept(t); } catch (Exception ex) {
                handler.accept(ex);
                if (next.isPresent()) {
                    next.get().accept(t);
                } else {
                    orResult.apply(ex);
                }
            }
        });
    }

    public static <T> Consumer<T> unsafe(CheckedConsumer<T> consumer) {
        return consumer;
    }

    public static <T, R> CheckedBuilder<Function<T, R>, CheckedFunction<T, R>, R> 
        safe(CheckedFunction<T, R> function) {
        return new CheckedWrapper<>(function, 
                (current, next, handler, orResult) -> t -> {
            try { return current.applyUnsafe(t); } catch (Exception ex) {
                handler.accept(ex);
                return next.isPresent() ? next.get().apply(t) : orResult.apply(ex);
            }
        });
    }

    public static <T, R> Function<T, R> unsafe(CheckedFunction<T, R> function) {
        return function;
    }

    @SuppressWarnings ("unchecked")
    static <T, E extends Throwable> T throwAsUnchecked(Throwable exception) throws E { 
        throw (E) exception; 
    }
}

@FunctionalInterface interface CheckedConsumer<T> extends Consumer<T> {
    void acceptUnsafe(T t) throws Exception;
    @Override default void accept(T t) {
        try { acceptUnsafe(t); } catch (Exception ex) {
            Try.throwAsUnchecked(ex);
        }
    }
}

@FunctionalInterface interface CheckedFunction<T, R> extends Function<T, R> {
    R applyUnsafe(T t) throws Exception;
    @Override default R apply(T t) {
        try { return applyUnsafe(t); } catch (Exception ex) {
            return Try.throwAsUnchecked(ex);
        }
    }
}

@FunctionalInterface interface CheckedSupplier<T> extends Supplier<T> {
    T getUnsafe() throws Exception;
    @Override default T get() {
        try { return getUnsafe(); } catch (Exception ex) {
            return Try.throwAsUnchecked(ex);
        }
    }
}

interface ReduceFunction<TSafe, TUnsafe, R> {
    TSafe wrap(TUnsafe current, Optional<TSafe> next, 
            Consumer<Throwable> handler, Function<Throwable, R> orResult);
}

interface CheckedBuilder<TSafe, TUnsafe, R> {
    CheckedBuilder<TSafe, TUnsafe, R> orTry(TUnsafe next);

    CheckedBuilder<TSafe, TUnsafe, R> handle(Consumer<Throwable> handler);

    <E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R> handle(
            Class<E> exceptionType, Consumer<E> handler);

    CheckedBuilder<TSafe, TUnsafe, R> handleLast(Consumer<Throwable> handler);

    <E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R> handleLast(
            Class<E> exceptionType, Consumer<? super E> handler);

    TSafe unsafe();
    TSafe rethrow(Function<Throwable, Exception> transformer);
    TSafe suppress();
    TSafe orElse(R value);
    TSafe orElseGet(Supplier<R> valueProvider);
}

final class CheckedWrapper<TSafe, TUnsafe, R> 
        implements CheckedBuilder<TSafe, TUnsafe, R> {

    private final TUnsafe function;
    private final ReduceFunction<TSafe, TUnsafe, R> reduceFunction;

    private final CheckedWrapper<TSafe, TUnsafe, R> root;
    private CheckedWrapper<TSafe, TUnsafe, R> next;

    private Consumer<Throwable> handlers = ex -> { };
    private Consumer<Throwable> lastHandlers = ex -> { };

    CheckedWrapper(TUnsafe function, 
            ReduceFunction<TSafe, TUnsafe, R> reduceFunction) {
        this.function = function;
        this.reduceFunction = reduceFunction;
        this.root = this;
    }

    private CheckedWrapper(TUnsafe function, 
            CheckedWrapper<TSafe, TUnsafe, R> prev) {
        this.function = function;
        this.reduceFunction = prev.reduceFunction;
        this.root = prev.root;
        prev.next = this;
    }

    @Override public CheckedBuilder<TSafe, TUnsafe, R> orTry(TUnsafe next) {
        return new CheckedWrapper<>(next, this);
    }

    @Override public CheckedBuilder<TSafe, TUnsafe, R> handle(
            Consumer<Throwable> handler) {
        handlers = handlers.andThen(handler);
        return this;
    }

    @Override public <E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R> 
        handle(Class<E> exceptionType, Consumer<E> handler) {
        handlers = handlers.andThen(ex -> {
            if (exceptionType.isInstance(ex)) {
                handler.accept(exceptionType.cast(ex));
            }
        });
        return this;
    }

    @Override public CheckedBuilder<TSafe, TUnsafe, R> handleLast(
            Consumer<Throwable> handler) {
        lastHandlers = lastHandlers.andThen(handler);
        return this;
    }

    @Override public <E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R> 
        handleLast(Class<E> exceptionType, Consumer<? super E> handler) {
        lastHandlers = lastHandlers.andThen(ex -> {
            if (exceptionType.isInstance(ex)) {
                handler.accept(exceptionType.cast(ex));
            }
        });
        return this;
    }

    @Override public TSafe unsafe() {
        return root.reduce(ex -> Try.throwAsUnchecked(ex));
    }

    @Override
    public TSafe rethrow(Function<Throwable, Exception> transformer) {
        return root.reduce(ex -> Try.throwAsUnchecked(transformer.apply(ex)));
    }

    @Override public TSafe suppress() {
        return root.reduce(ex -> null);
    }

    @Override public TSafe orElse(R value) {
        return root.reduce(ex -> value);
    }

    @Override public TSafe orElseGet(Supplier<R> valueProvider) {
        Objects.requireNonNull(valueProvider);
        return root.reduce(ex -> valueProvider.get());
    }

    private TSafe reduce(Function<Throwable, R> orResult) {
        return reduceFunction.wrap(function, 
                Optional.ofNullable(next).map(p -> p.reduce(orResult)), 
                this::handle, orResult);
    }

    private void handle(Throwable ex) {
        for (CheckedWrapper<TSafe, TUnsafe, R> current = this; 
                current != null; 
                current = current.next) {
            current.handlers.accept(ex);
        }
        lastHandlers.accept(ex);
    }
}
 3
Author: introspected,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-23 09:48:18

Używam tego rodzaju wyjątku:

public class CheckedExceptionWrapper extends RuntimeException {
    ...
    public <T extends Exception> CheckedExceptionWrapper rethrow() throws T {
        throw (T) getCause();
    }
}

Będzie wymagało obsługi tych wyjątków statycznie:

void method() throws IOException, ServletException {
    try { 
        list.stream().forEach(object -> {
            ...
            throw new CheckedExceptionWrapper(e);
            ...            
        });
    } catch (CheckedExceptionWrapper e){
        e.<IOException>rethrow();
        e.<ServletExcepion>rethrow();
    }
}

Chociaż wyjątek i tak zostanie ponownie wyrzucony podczas pierwszego rethrow() wywołania (oh, Java generics...), w ten sposób można uzyskać ścisłą statyczną definicję możliwych WYJĄTKÓW (wymaga zadeklarowania ich w throws). I nie instanceof lub coś jest potrzebne.

 1
Author: Taras,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-13 17:41:15

Zgadzam się z powyższymi komentarzami, używając Stream.map jesteś ograniczony do implementacji funkcji, która nie wyrzuca WYJĄTKÓW.

Możesz jednak utworzyć własną interfejs funkcyjny, który rzuca jak poniżej..

@FunctionalInterface
public interface UseInstance<T, X extends Throwable> {
  void accept(T instance) throws X;
}

Następnie zaimplementuj go używając lambda lub referencji, jak pokazano poniżej.

import java.io.FileWriter;
import java.io.IOException;

//lambda expressions and the execute around method (EAM) pattern to
//manage resources

public class FileWriterEAM  {
  private final FileWriter writer;

  private FileWriterEAM(final String fileName) throws IOException {
    writer = new FileWriter(fileName);
  }
  private void close() throws IOException {
    System.out.println("close called automatically...");
    writer.close();
  }
  public void writeStuff(final String message) throws IOException {
    writer.write(message);
  }
  //...

  public static void use(final String fileName, final UseInstance<FileWriterEAM, IOException> block) throws IOException {

    final FileWriterEAM writerEAM = new FileWriterEAM(fileName);    
    try {
      block.accept(writerEAM);
    } finally {
      writerEAM.close();
    }
  }

  public static void main(final String[] args) throws IOException {

    FileWriterEAM.use("eam.txt", writerEAM -> writerEAM.writeStuff("sweet"));

    FileWriterEAM.use("eam2.txt", writerEAM -> {
        writerEAM.writeStuff("how");
        writerEAM.writeStuff("sweet");      
      });

    FileWriterEAM.use("eam3.txt", FileWriterEAM::writeIt);     

  }


 void writeIt() throws IOException{
     this.writeStuff("How ");
     this.writeStuff("sweet ");
     this.writeStuff("it is");

 }

}
 0
Author: JohnnyO,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-30 22:45:11

Jedynym wbudowanym sposobem obsługi sprawdzonych wyjątków, które mogą być wyrzucane przez operację map, jest zamknięcie ich wewnątrz CompletableFuture. (An Optional jest prostszą alternatywą, jeśli nie musisz zachować wyjątku.) Klasy te mają na celu umożliwienie reprezentowania operacji warunkowych w sposób funkcjonalny.

Wymagane jest kilka nietrywialnych metod pomocniczych, ale można dojść do kodu, który jest stosunkowo zwięzły, jednocześnie dając do zrozumienia, że wynik transmisji zależy od pomyślnego zakończenia operacji map. Oto jak to wygląda:

    CompletableFuture<List<Class<?>>> classes =
            Stream.of("java.lang.String", "java.lang.Integer", "java.lang.Double")
                  .map(MonadUtils.applyOrDie(Class::forName))
                  .map(cfc -> cfc.thenApply(Class::getSuperclass))
                  .collect(MonadUtils.cfCollector(ArrayList::new,
                                                  List::add,
                                                  (List<Class<?>> l1, List<Class<?>> l2) -> { l1.addAll(l2); return l1; },
                                                  x -> x));
    classes.thenAccept(System.out::println)
           .exceptionally(t -> { System.out.println("unable to get class: " + t); return null; });

Daje to następujące wyjście:

[class java.lang.Object, class java.lang.Number, class java.lang.Number]

The applyOrDie metoda pobiera Function, który wyrzuca wyjątek i przekształca go w Function, który zwraca już zakończony CompletableFuture -- albo zakończony normalnie z wynikiem oryginalnej funkcji, albo zakończony wyjątkowo z wyrzuconym wyjątkiem.

Druga map Operacja ilustruje, że teraz masz Stream<CompletableFuture<T>> zamiast tylko Stream<T>. CompletableFuture zajmuje się wykonywaniem tej operacji tylko wtedy, gdy operacja upstream się powiodła. API sprawia, że jest to wyjaśnione, ale stosunkowo bezbolesne.

Dopóki nie dojdziesz do fazy collect, czyli. W tym miejscu potrzebujemy dość znaczącej metody pomocniczej. Chcemy "podnieść" normalną operację zbioru (w tym przypadku toList()) "wewnątrz"CompletableFuture -- cfCollector() pozwala nam to zrobić za pomocą supplier, accumulator, combiner, oraz finisher, które nie muszą wiedzieć nic o CompletableFuture.

Metody pomocnicze można znaleźć na Githubie w moim MonadUtils klasy, która jest nadal w toku.

 0
Author: Matt McHenry,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-01 01:27:09

Myślę, że to podejście jest właściwe:

public List<Class> getClasses() throws ClassNotFoundException {
    List<Class> classes;
    try {
        classes = Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String").map(className -> {
            try {
                return Class.forName(className);
            } catch (ClassNotFoundException e) {
                throw new UndeclaredThrowableException(e);
            }
        }).collect(Collectors.toList());
    } catch (UndeclaredThrowableException e) {
        if (e.getCause() instanceof ClassNotFoundException) {
            throw (ClassNotFoundException) e.getCause();
        } else {
            // this should never happen
            throw new IllegalStateException(e.getMessage(), e);
        }
    }
    return classes;
}

Zawijanie wyjątku sprawdzonego wewnątrz Callable w UndeclaredThrowableException (to jest przypadek użycia tego wyjątku) i rozpakowywanie go na zewnątrz.

Tak, uważam to za brzydkie, i odradzam używanie lambda w tym przypadku i po prostu wycofuję się do starej, dobrej pętli, chyba że pracujesz z równoległym strumieniem i paralelizacja przynosi obiektywne korzyści, które uzasadniają nieczytelność kodu.

Jak wielu innych wskazało istnieją rozwiązania tej sytuacji i mam nadzieję, że jedno z nich przerodzi się w przyszłą wersję Javy.

 0
Author: Paramaeleon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-24 08:10:20

Prawdopodobnie lepszym i bardziej funkcjonalnym sposobem jest owijanie wyjątków i rozprzestrzenianie ich dalej w strumieniu. Spójrz na przykład na typ Try Vavr .

Przykład:

interface CheckedFunction<I, O> {
    O apply(I i) throws Exception; }

static <I, O> Function<I, O> unchecked(CheckedFunction<I, O> f) {
    return i -> {
        try {
            return f.apply(i);
        } catch(Exception ex) {

            throw new RuntimeException(ex);
        }
    } }

fileNamesToRead.map(unchecked(file -> Files.readAllLines(file)))

Lub

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

static <I, O> Function<I, O> unchecked(CheckedFunction<I, O> f) {
    return arg -> {
        try {
            return f.apply(arg);
        } catch(Exception ex) {
            return throwUnchecked(ex);
        }
    };
}

Druga implementacja pozwala uniknąć zawijania wyjątku w RuntimeException. throwUnchecked działa, ponieważ prawie zawsze wszystkie ogólne wyjątki są traktowane jako niezaznaczone w Javie.

 0
Author: Mikhail Kholodkov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-18 08:56:57

TL; DR just use Lombok ' s @SneakyThrows.

Christian Hujer wyjaśnił już szczegółowo, dlaczego wyrzucanie sprawdzonych WYJĄTKÓW ze strumienia jest, ściśle mówiąc, niemożliwe ze względu na ograniczenia Javy.

Niektóre inne odpowiedzi wyjaśniły sztuczki, aby ominąć ograniczenia języka, ale nadal są w stanie spełnić wymóg rzucania "samego sprawdzonego wyjątku i bez dodawania brzydkich prób / łapek do strumienia" , niektóre z nich wymagają dziesiątek dodatkowych linii kotła.

Podkreślę jeszcze jedną opcję, że IMHO jest o wiele czystsza od wszystkich innych: Lombok ' s @SneakyThrows. Zostało wspomniane przez inne odpowiedzi, ale zostało trochę zakopane pod wieloma niepotrzebnymi szczegółami.

Otrzymany kod jest tak prosty jak:

public List<Class> getClasses() throws ClassNotFoundException {
    List<Class> classes =
        Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
                .map(className -> getClass(className))
                .collect(Collectors.toList());
    return classes;
}

@SneakyThrows                                 // <= this is the only new code
private Class<?> getClass(String className) {
    return Class.forName(className);
}

Potrzebowaliśmy tylko jednej Extract Method refaktoryzacji (wykonanej przez IDE) i jednej dodatkowej linii dla @SneakyThrows. Adnotacja dba o dodanie wszystkich kotłów do upewnij się, że możesz rzucić swój wyjątek checked bez zawijania go w RuntimeException i bez konieczności deklarowania go jawnie.

 0
Author: sergut,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-09 12:12:45