Come posso generare le eccezioni CHECKED dagli stream Java 8?


287

Come posso generare eccezioni CHECKED dall'interno di Java 8 stream / lambdas?

In altre parole, voglio creare un codice come questo compilare:

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

Questo codice non viene compilato, poiché viene lanciato il Class.forName()metodo sopra ClassNotFoundException, che viene verificato.

Notare che NON voglio racchiudere l'eccezione selezionata all'interno di un'eccezione di runtime e invece lanciare l'eccezione non selezionata. Voglio lanciare l'eccezione selezionata stessa e senza aggiungere brutto try/ catchesallo stream.


42
La risposta breve è, non è possibile, senza infrangere le regole relative alle eccezioni. Puoi imbrogliare, ovviamente, e gli altri ti mostreranno volentieri come, ma tieni presente che questo è un imbroglio e tale imbroglio spesso torna a morderti. Dovresti prendere l'eccezione e affrontarla. Se si desidera racchiuderlo, quindi ricominciare l'eccezione verificata, è possibile farlo in modo sicuro.
Brian Goetz,

35
@Brian, non ho bisogno che altri mi dicano come imbrogliare, so come imbrogliare me stesso e ho pubblicato il mio modo di imbrogliare nella risposta di seguito, che hai annullato. So che sei coinvolto nella discussione Java che ha deciso che non c'era un buon modo di gestire le eccezioni verificate negli stream, quindi trovo sorprendente che tu abbia notato questa mia domanda, ma sono deluso dalla tua risposta che dice semplicemente "questo è non va bene ", non dà alcun motivo per cui, e poi si passa ad aggiungere nuovamente try / catch.
MarcG

22
@Brian, francamente, in pratica quando le persone cercano di refactoring di dichiarazioni for-legacy, la metà di loro viene convertita in stream ma l'altra metà rinuncia a refactoring, perché nessuno vuole aggiungere questi try / catch. Rendono il codice molto più difficile da leggere, sicuramente più delle dichiarazioni originali. Nel mio esempio di codice sopra, fintanto che mantieni il "lancio ClassNotFoundException", non vedo alcuna differenza rispetto al codice esterno. Potresti darmi alcuni esempi di vita reale in cui ciò infrange le regole relative alle eccezioni?
MarcG

10
La scrittura di metodi wrapper che vanno a capo di eccezioni non controllate risolve l'obiezione "ingombro di codice" e non interrompe il sistema dei tipi. La risposta qui che ricorre a un "tiro subdolo" di un'eccezione verificata interrompe il sistema dei tipi, poiché il codice chiamante non si aspetterà (né sarà consentito di catturare) l'eccezione verificata.
Brian Goetz,

14
Non affronta l'obiezione del disordine del codice perché è quindi necessario un secondo tentativo / catch intorno allo stream, per scartare e ricodificare l'eccezione originale. Al contrario, se si genera l'eccezione selezionata, è sufficiente mantenere la throws ClassNotFoundExceptiondichiarazione del metodo che contiene il flusso, in modo che il codice chiamante si aspetti e sia autorizzato a rilevare l'eccezione selezionata.
MarcG,

Risposte:


250

La semplice risposta alla tua domanda è: non puoi, almeno non direttamente. E non è colpa tua. Oracle l'ha incasinato. Si aggrappano al concetto di eccezioni verificate, ma hanno inavvertitamente dimenticato di occuparsi delle eccezioni verificate durante la progettazione di interfacce funzionali, stream, lambda ecc.

A mio parere, questo è un grande errore nella API e un piccolo bug nella specifica del linguaggio .

Il bug nell'API è che non offre alcuna possibilità di inoltrare eccezioni verificate in cui ciò avrebbe davvero un senso terribile per la programmazione funzionale. Come dimostrerò di seguito, una tale struttura sarebbe stata facilmente possibile.

Il bug nella specifica del linguaggio è che non consente a un parametro di tipo di inferire un elenco di tipi anziché un singolo tipo purché il parametro di tipo venga utilizzato solo in situazioni in cui è consentito un elenco di tipi ( throwsclausola).

La nostra aspettativa come programmatori Java è che il seguente codice dovrebbe essere compilato:

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

Tuttavia, dà:

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

Il modo in cui vengono definite le interfacce funzionali attualmente impedisce al compilatore di inoltrare l'eccezione - non v'è alcuna dichiarazione, che avrebbe detto Stream.map()che se Function.apply() throws E, Stream.map() throws Epure.

Ciò che manca è una dichiarazione di un parametro di tipo per passare attraverso le eccezioni verificate. Il codice seguente mostra come un simile parametro di tipo pass-through avrebbe potuto essere dichiarato con la sintassi corrente. Ad eccezione del caso speciale nella riga contrassegnata, che è un limite discusso di seguito, questo codice viene compilato e si comporta come previsto.

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

Nel caso throwSomeMoreci piacerebbe vedere che ci siamo IOExceptionpersi, ma in realtà manca Exception.

Questo non è perfetto perché l'inferenza del tipo sembra essere alla ricerca di un singolo tipo, anche in caso di eccezioni. Poiché l'inferenza di tipo ha bisogno di un singolo tipo, Edeve risolversi in un comune superdi ClassNotFoundExceptione IOException, che è Exception.

È necessaria una modifica alla definizione dell'inferenza del tipo in modo che il compilatore cercherà più tipi se viene utilizzato il parametro type in cui è consentito un elenco di tipi ( throwsclausola). Quindi il tipo di eccezione riportato dal compilatore sarebbe specifico quanto la throwsdichiarazione originale delle eccezioni verificate del metodo di riferimento, non un singolo super tipo generico.

La cattiva notizia è che questo significa che Oracle ha fatto un casino. Certamente non romperanno il codice di terra dell'utente, ma l'introduzione di parametri di tipo di eccezione nelle interfacce funzionali esistenti interromperebbe la compilazione di tutto il codice di terra dell'utente che utilizza esplicitamente queste interfacce. Dovranno inventare un po 'di zucchero sintattico per risolvere questo problema.

La notizia ancora peggiore è che questo argomento è già stato discusso da Brian Goetz nel 2010 https://blogs.oracle.com/briangoetz/entry/exception_transparency_in_java (nuovo link: http://mail.openjdk.java.net/pipermail/lambda -dev / 2010-June / 001484.html ) ma sono informato che alla fine questa indagine non è andata a buon fine e che in Oracle non ci sono lavori che conosco per mitigare le interazioni tra eccezioni verificate e lambdas.


16
Interessante. Credo che alcune persone apprezzino i flussi per consentire un codice parallelo più semplice, mentre altri per consentire un codice più pulito. Brian Goetz ovviamente si preoccupa di più del parallelismo (da quando ha scritto Java Concurrency in Practice), mentre Robert Martin si preoccupa di più del codice pulito (da quando ha scritto il libro Clean Code). I tentativi / catture della caldaia sono un prezzo minore da pagare per il parallelismo, quindi non c'è da meravigliarsi che Brian Goetz non sia spaventato dai problemi dell'utilizzo delle eccezioni verificate all'interno dei flussi. Inoltre, non c'è da meravigliarsi se Robert Martin odia le eccezioni verificate poiché si aggiungono al disordine.
MarcG,

5
Prevedo che, tra qualche anno, la difficoltà di gestire le eccezioni verificate all'interno dei flussi porterà a uno di questi due risultati: le persone smetteranno semplicemente di usare le eccezioni verificate, O tutti inizieranno a usare un hack molto simile a quello in cui ho postato la mia risposta UtilException. Scommetto che i flussi Java-8 sono l'ultimo chiodo sulla bara delle eccezioni verificate, non erano per il fatto che le eccezioni verificate fanno parte del JDK. Anche se mi piacciono e utilizzo le eccezioni verificate nel codice aziendale (per alcuni casi d'uso specifici), avrei preferito tutte le eccezioni JDK comuni estese il runtime.
MarcG,

9
@Unihedro Il problema rimane che le interfacce funzionali non inoltrano eccezioni. Avrei bisogno del try-catchblocco all'interno della lambda, e questo semplicemente non ha alcun senso. Non appena Class.forNameviene utilizzato in qualche modo nella lambda, ad esempio in names.forEach(Class::forName), il problema è presente. Fondamentalmente, i metodi che generano eccezioni verificate sono stati esclusi dalla partecipazione alla programmazione funzionale direttamente come interfacce funzionali, dal design (scarso!).
Christian Hujer,

26
@ChristianHujer L'esplorazione "Trasparenza delle eccezioni" era proprio questo: un'esplorazione (che ha avuto origine dalla proposta BGGA). Dopo un'analisi più approfondita, abbiamo scoperto che offriva uno scarso equilibrio di valore e complessità, e presentava alcuni seri problemi (ha portato a problemi di inferenza indecidibili e "catturare X" era insoddisfacente, tra gli altri.) È estremamente comune che un'idea del linguaggio sembra promettente - anche "ovvio" - ma dopo un'esplorazione più profonda, si è rivelato difettoso. Questo era uno di quei casi.
Brian Goetz,

13
@BrianGoetz Ci sono alcune informazioni pubbliche disponibili sui problemi di inferenza indecidibili che hai menzionato? Sono curioso e vorrei capirlo.
Christian Hujer,

169

Questa LambdaExceptionUtilclasse di supporto consente di utilizzare tutte le eccezioni verificate nei flussi Java, in questo modo:

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

Class::forNameLancia la nota ClassNotFoundException, che è selezionata . Anche il flusso stesso genera ClassNotFoundException, e NON alcune eccezioni non verificate.

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

}

Molti altri esempi su come usarlo (dopo l'importazione statica 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");
    }    

NOTA 1: I rethrowmetodi della LambdaExceptionUtilclasse precedente possono essere utilizzati senza paura e possono essere utilizzati in qualsiasi situazione . Un grande ringraziamento all'utente @PaoloC che ha contribuito a risolvere l'ultimo problema: ora il compilatore ti chiederà di aggiungere clausole di lancio e tutto è come se potessi lanciare nativamente le eccezioni verificate sui flussi Java 8.


NOTA 2: I uncheckmetodi della LambdaExceptionUtilclasse precedente sono metodi bonus e possono essere rimossi in modo sicuro dalla classe se non si desidera utilizzarli. Se li hai usati, fallo con cura e non prima di aver compreso i seguenti casi d'uso, vantaggi / svantaggi e limitazioni:

• È possibile utilizzare i uncheckmetodi se si chiama un metodo che letteralmente non può mai generare l'eccezione dichiarata. Ad esempio: new String (byteArr, "UTF-8") genera UnsupportedEncodingException, ma UTF-8 è garantito dalle specifiche Java sempre presenti. Qui, la dichiarazione di lancio è una seccatura e qualsiasi soluzione per silenziarla con una piastra di cottura minima è benvenuta:String text = uncheck(() -> new String(byteArr, "UTF-8"));

• È possibile utilizzare i uncheckmetodi se si sta implementando un'interfaccia rigorosa in cui non si ha la possibilità di aggiungere una dichiarazione dei tiri, e tuttavia lanciare un'eccezione è del tutto appropriato. Avvolgere un'eccezione solo per ottenere il privilegio di lanciarla si traduce in uno stack stack con eccezioni spurie che non forniscono informazioni su ciò che è effettivamente andato storto. Un buon esempio è Runnable.run (), che non genera alcuna eccezione verificata.

• In ogni caso, se decidi di utilizzare i uncheckmetodi, tieni presente queste 2 conseguenze del lancio di eccezioni CONTROLLATE senza una clausola di lancio: 1) Il codice chiamante non sarà in grado di prenderlo per nome (se provi, il il compilatore dirà: l'eccezione non viene mai generata nel corpo dell'istruzione try corrispondente). Bolle e probabilmente sarà catturato nel ciclo principale del programma da qualche "catch Exception" o "catch Throwable", che può essere comunque quello che vuoi. 2) viola il principio della minima sorpresa: non sarà più sufficiente catturare RuntimeExceptionper poter garantire la cattura di tutte le possibili eccezioni. Per questo motivo, credo che ciò non dovrebbe essere fatto nel codice del framework, ma solo nel codice aziendale che controlli completamente.


4
Ritengo che questa risposta sia stata ingiustamente ridimensionata. Il codice funziona. Le eccezioni verificate dovrebbero essere lanciate o trattate. Se vuoi lanciarli, mantieni la "clausola dei tiri" nel metodo che contiene lo stream. Ma se vuoi affrontarli semplicemente avvolgendo e ricodificando, immagino che preferisco usare il codice sopra per "deselezionare" le eccezioni e lasciarle fare bolle da sole. L'unica differenza di cui sono a conoscenza è che l'eccezione gorgogliante non estenderà RuntimeException. So che ai puristi non piacerà, ma questo "inevitabilmente tornerà a mordere qualcuno"? Non sembra probabile.
MarcG

4
@Christian Hujer, a dire il vero con il downvoter, ha retrocesso una versione precedente prima di aggiungere la spiegazione "vantaggi, svantaggi e limitazioni". Quindi forse era meritato al momento. Non puoi insegnare a qualcuno come infrangere le regole senza almeno cercare di capire e spiegare le conseguenze. Il motivo principale per cui ho pubblicato questa domanda è stato quello di ottenere feedback per gli svantaggi della mia risposta. Ho finito per ottenere questo feedback non qui, ma da un'altra domanda in programmers.stackexchange. Poi sono tornato qui e ho aggiornato la mia risposta.
MarcG,

16
Ho effettuato il downvoting solo perché questo incoraggia il codice non mantenibile . Questo è un brutto hack, anche se intelligente, e non troverò mai questa risposta utile. Anche questo è un altro "non usare" della lingua.
Unihedron,

12
@Unihedro ma perché diventa non mantenibile? Non vedo perché. Qualche esempio?
MarcG,

2
A mio avviso, l' @SuppressWarnings ("unchecked")inganno del compilatore è assolutamente inaccettabile.
Thorbjørn Ravn Andersen,

26

Non puoi farlo in sicurezza. Puoi imbrogliare, ma poi il tuo programma si interrompe e questo inevitabilmente tornerà a mordere qualcuno (dovresti essere tu, ma spesso il nostro tradimento esplode su qualcun altro.)

Ecco un modo leggermente più sicuro per farlo (ma ancora non lo consiglio.)

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

Qui, quello che stai facendo è catturare l'eccezione nella lambda, emettere un segnale dalla pipeline del flusso che indica che il calcolo non è riuscito in modo eccezionale, catturare il segnale e agire su quel segnale per lanciare l'eccezione sottostante. La chiave è che stai sempre rilevando l'eccezione sintetica, piuttosto che consentire a un'eccezione controllata di fuoriuscire senza dichiarare che viene generata l'eccezione.


18
Solo una domanda; qual è stata la decisione progettuale che ha portato i lambda a non essere in grado di propagare le eccezioni verificate dal loro contesto? Si noti che capisco che le interfacce funzionali come Functionecc. Non fanno throwsnulla; Sono solo curioso.
fge,

4
Ciò throw w.cause;non farebbe lamentare il compilatore che il metodo non genera né cattura Throwable? Quindi, è probabile che IOExceptionci sia bisogno di un cast . Inoltre, se il lambda genera più di un tipo di eccezione verificata, il corpo del pescato diventerebbe un po 'brutto con alcuni instanceofcontrolli (o qualcos'altro con uno scopo simile) per verificare quale eccezione verificata è stata lanciata.
Victor Stafusa,

10
@schatten Uno dei motivi è che potresti dimenticare di catturare WE, e quindi una strana eccezione (che nessuno sa come gestire) potrebbe fuoriuscire. (Potresti dire "ma hai colto l'eccezione, quindi è sicuro." In questo esempio di giocattolo. Ma ogni volta che ho visto una base di codice adottare questo approccio, alla fine qualcuno dimentica. La tentazione di ignorare le eccezioni non conosce limiti.) Un altro rischio è che usarlo in sicurezza è specifico per una particolare combinazione (usa sito, eccezione). Non si adatta bene a più eccezioni o usi non omogenei.
Brian Goetz,

2
@hoodaticus Sono d'accordo con te. Detto questo, preferisci avvolgere sempre di più (come mostrato sopra, aumentando il rischio di "dimenticare") o semplicemente creare 4 interfacce intelligenti e utilizzare lambdas senza avvolgimento, come mostrato in stackoverflow.com/a/30974991/2365724 ? Grazie
PaoloC,

10
Francamente, questa soluzione è completamente inattuabile. Ho pensato che il punto dei flussi fosse ridurre la piastra di cottura, non aumentarla.
wvdz,

24

Puoi!

Estensione di @marcg UtilExceptione aggiunta throw Edove necessario: in questo modo, il compilatore ti chiederà di aggiungere clausole di lancio e tutto come se potessi lanciare nativamente le eccezioni verificate sui flussi di java 8.

Istruzioni: copia / incolla il LambdaExceptionUtiltuo IDE e poi usalo come mostrato di seguito 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;
    }

}

Alcuni test per mostrare l'utilizzo e il comportamento:

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

}

1
Scusa @setheron hai ragione, basta aggiungere <Integer>prima map. In effetti, il compilatore java non può inferire il Integertipo restituito. Tutto il resto dovrebbe essere corretto.
PaoloC,

1
Questo ha funzionato per me. Ha reso perfetta la risposta di MarcG applicando la gestione dell'eccezione.
Skychan,

1
Soluzione al problema precedente: dichiarare variabile come questa espressione <ThingType> del consumatore = rethrowConsumer ((cosa ThingType) -> thing.clone ()); quindi usa quell'espressione all'interno della foreach interna.
Skychan,

1
@Skychan: Dal momento che in questa nuova versione modificata non stai più sopprimendo alcuna eccezione, è probabilmente un po 'più difficile per il sistema di inferenza. In alcuni commenti qui sotto Brian Goetz parla della "trasparenza delle eccezioni" che porta a "problemi di inferenza indecidibili".
MarcG,

3
Molto bella. L'unica cosa spiacevole è che non funziona perfettamente con un metodo che genera più eccezioni verificate. In questo caso il compilatore ti farà catturare un supertipo comune, ad es Exception.
wvdz,

12

Basta utilizzare uno qualsiasi dei NoException (mio progetto), non controllato di jOOλ , gettando-lambda , interfacce Throwable o 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));

7

Ho scritto una libreria che estende l'API Stream per consentire di generare eccezioni verificate. Usa il trucco di Brian Goetz.

Il tuo codice diventerebbe

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

7

Questa risposta è simile a 17 ma evita la definizione dell'eccezione wrapper:

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
        }

1
Questa è una soluzione semplice ed efficace.
Lin W,

2
Questo è esattamente ciò che Op non voleva: provare i blocchi nella lambda. Inoltre, funziona solo come previsto purché nessun altro codice al di fuori del blocco try includa una IOException in una RuntimeException. Per evitare ciò, è possibile utilizzare un wrapper-RuntimeException personalizzato (definito come classe interna privata).
Malte Hartwig,

5

Non puoi.

Tuttavia, potresti voler dare un'occhiata a uno dei miei progetti che ti permette di manipolare più facilmente tali "lanciare lambda".

Nel tuo caso, sarai in grado di farlo:

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

e catturare MyException.

Questo è un esempio. Un altro esempio è che potresti avere .orReturn()un valore predefinito.

Si noti che questo è ANCORA un lavoro in corso, ne arriveranno altri. Nomi migliori, più funzionalità ecc.


2
Ma poi, se vuoi lanciare l'eccezione originale selezionata dovrai aggiungere il tentativo / cattura attorno allo stream, per scartarlo, il che è ancora terribile! Mi piace l'idea che PUOI lanciare un'eccezione non selezionata, se lo desideri, e che PUOI restituire un valore predefinito allo stream, se lo desideri, ma penso anche che dovresti aggiungere un .orThrowChecked()metodo al tuo progetto che consenta di generare l'eccezione selezionata . Dai un'occhiata alla mia UtilExceptionrisposta in questa pagina e vedi se ti piace l'idea di aggiungere questa terza possibilità al tuo progetto.
MarcG,

"Ma poi, se vuoi lanciare l'eccezione originale controllata dovrai aggiungere il tentativo / cattura attorno allo stream, per scartarlo, il che è ancora terribile!" <- Sì, ma non hai scelta. Lambdas non è in grado di propagare le eccezioni verificate al di fuori del loro contesto, questa è una "decisione" progettuale (la considero un difetto, personalmente, ma vabbe)
fge,

Quanto alla tua idea, non seguo molto bene quello che fa, scusa; dopo tutto, lo consideri ancora non controllato, quindi in che modo è diverso da quello che faccio? (tranne che ho un'interfaccia diversa per esso)
fge

Ad ogni modo, sei il benvenuto per contribuire al progetto! Inoltre, hai notato che Streamimplementa AutoCloseable?
fge,

Lascia che ti chieda questo: il tuo MyExceptionsopra deve essere un'eccezione non selezionata?
MarcG,

3

Riassumendo i commenti sopra la soluzione avanzata è usare un wrapper speciale per funzioni non controllate con builder come API che fornisce recupero, ri-lancio e suppresing.

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

Il codice seguente lo dimostra per le interfacce Consumer, Supplier e Function. Può essere facilmente espanso. Alcune parole chiave pubbliche sono state rimosse per questo esempio.

Class Try è l'endpoint per il codice client. I metodi sicuri possono avere un nome univoco per ciascun tipo di funzione. CheckedConsumer , CheckedSupplier e CheckedFunction vengono controllati analoghi di funzioni lib che possono essere utilizzati separatamente Prova

CheckedBuilder è l'interfaccia per la gestione delle eccezioni in alcune funzioni selezionate. oppure Try consente di eseguire un'altra funzione dello stesso tipo in caso di errore precedente. handle fornisce la gestione delle eccezioni, incluso il filtro del tipo di eccezione. L'ordine dei gestori è importante. Ridurre i metodi non sicuri e riproporre nuovamente l'ultima eccezione nella catena di esecuzione. Riduci i metodi orElse e orElseGet restituiscono un valore alternativo come quelli Opzionali se tutte le funzioni falliscono. Inoltre c'è la soppressione del metodo . CheckedWrapper è l'implementazione comune di 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

TL; DR Usa solo Lombok@SneakyThrows .

Christian Hujer ha già spiegato in dettaglio perché non è possibile lanciare eccezioni verificate da uno stream a causa delle limitazioni di Java.

Alcune altre risposte hanno spiegato i trucchi per aggirare i limiti della lingua ma essere ancora in grado di soddisfare il requisito di lanciare "l'eccezione controllata stessa e senza aggiungere brutti tentativi / catture allo stream" , alcuni dei quali richiedono decine di righe aggiuntive del boilerplate.

Ho intenzione di evidenziare un'altra opzione per fare questo che IMHO è molto più pulito di tutti gli altri: quello di Lombok @SneakyThrows. È stato menzionato di passaggio da altre risposte, ma è stato un po 'sepolto sotto molti dettagli inutili.

Il codice risultante è semplice come:

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

Avevamo solo bisogno di un Extract Methodrefactoring (eseguito dall'IDE) e di una riga aggiuntiva per @SneakyThrows. L'annotazione si occupa di aggiungere tutta la piastra della caldaia per assicurarsi di poter lanciare l'eccezione selezionata senza racchiuderla in una RuntimeExceptione senza la necessità di dichiararla esplicitamente.


4
L'uso di lombok dovrebbe essere scoraggiato.
Dragas,

2

È inoltre possibile scrivere un metodo wrapper per racchiudere le eccezioni non selezionate e persino migliorare il wrapper con un parametro aggiuntivo che rappresenta un'altra interfaccia funzionale (con lo stesso tipo di ritorno R ). In questo caso è possibile passare una funzione che verrebbe eseguita e restituita in caso di eccezioni. Vedi l'esempio seguente:

private void run() {
    List<String> list = Stream.of(1, 2, 3, 4).map(wrapper(i ->
            String.valueOf(++i / 0), i -> String.valueOf(++i))).collect(Collectors.toList());
    System.out.println(list.toString());
}

private <T, R, E extends Exception> Function<T, R> wrapper(ThrowingFunction<T, R, E> function, 
Function<T, R> onException) {
    return i -> {
        try {
            return function.apply(i);
        } catch (ArithmeticException e) {
            System.out.println("Exception: " + i);
            return onException.apply(i);
        } catch (Exception e) {
            System.out.println("Other: " + i);
            return onException.apply(i);
        }
    };
}

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

2

Ecco una visione o una soluzione diversa per il problema originale. Qui mostro che abbiamo un'opzione per scrivere un codice che elaborerà solo un sottoinsieme valido di valori con un'opzione per rilevare e gestire i casi quando è stata generata l'eccezione.

    @Test
    public void getClasses() {

        String[] classNames = {"java.lang.Object", "java.lang.Integer", "java.lang.Foo"};
        List<Class> classes =
                Stream.of(classNames)
                        .map(className -> {
                            try {
                                return Class.forName(className);
                            } catch (ClassNotFoundException e) {
                                // log the error
                                return null;
                            }
                        })
                        .filter(c -> c != null)
                        .collect(Collectors.toList());

        if (classes.size() != classNames.length) {
            // add your error handling here if needed or process only the resulting list
            System.out.println("Did not process all class names");
        }

        classes.forEach(System.out::println);
    }

1

Sono d'accordo con i commenti sopra, usando Stream.map sei limitato all'implementazione della Funzione che non genera Eccezioni.

Puoi comunque creare la tua FunctionalInterface che getta come di seguito.

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

quindi implementalo usando Lambdas o riferimenti come mostrato di seguito.

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

 }

}

1

L'unico modo integrato di gestire le eccezioni verificate che possono essere generate da mapun'operazione è incapsularle in a CompletableFuture. (Un'alternativa Optionalè più semplice se non è necessario conservare l'eccezione.) Queste classi hanno lo scopo di consentire di rappresentare le operazioni contingenti in modo funzionale.

Sono richiesti un paio di metodi di supporto non banali, ma è possibile arrivare a un codice relativamente conciso, pur rendendo evidente che il risultato del flusso è subordinato al mapcompletamento dell'operazione. Ecco come appare:

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

Questo produce il seguente output:

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

Il applyOrDiemetodo prende una Functionche genera un'eccezione e la converte in una Functionche restituisce una già completata CompletableFuture, completata normalmente con il risultato della funzione originale o completata in modo eccezionale con l'eccezione generata.

La seconda mapoperazione mostra che ora hai un Stream<CompletableFuture<T>>invece di solo un Stream<T>. CompletableFuturesi occupa di eseguire questa operazione solo se l'operazione a monte ha avuto esito positivo. L'API rende questo esplicito, ma relativamente indolore.

Fino a quando non si arriva alla collectfase, cioè. È qui che richiediamo un metodo di supporto piuttosto significativo. Vogliamo "lift" un'operazione di raccolta normale (in questo caso, toList()) "dentro" il CompletableFuture- cfCollector()ci permette di farlo utilizzando un supplier, accumulator, combiner, e finisherche non hanno bisogno di sapere qualche cosa riguardo CompletableFuture.

I metodi di supporto sono disponibili su GitHub nella mia MonadUtilsclasse, che è ancora in fase di elaborazione.


1

Probabilmente, un modo migliore e più funzionale è avvolgere le eccezioni e diffonderle ulteriormente nel flusso. Dai un'occhiata al tipo Try di Vavr per esempio.

Esempio:

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

O

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

La seconda implementazione evita di racchiudere l'eccezione in a RuntimeException. throwUncheckedfunziona perché quasi sempre tutte le eccezioni generiche vengono trattate come non selezionate in Java.


1

Uso questo tipo di eccezione per il wrapping:

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

Richiederà la gestione statica di queste eccezioni:

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

Provalo online!

Sebbene l'eccezione verrà comunque rilanciata durante la prima rethrow()chiamata (oh, generici Java ...), in questo modo è possibile ottenere una definizione statica rigorosa delle possibili eccezioni (è necessario dichiararle throws). E non instanceofè necessario o qualcosa del genere.


-1

Penso che questo approccio sia quello giusto:

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

Avvolgere l'eccezione controllata all'interno di Callablein UndeclaredThrowableException(questo è il caso d'uso per questa eccezione) e scartarla all'esterno.

Sì, lo trovo brutto e consiglierei di non usare lambda in questo caso e ricadere in un buon vecchio ciclo, a meno che tu non stia lavorando con un flusso parallelo e la paralellizzazione porti un vantaggio oggettivo che giustifica la illeggibilità del codice.

Come molti altri hanno sottolineato, ci sono soluzioni a questa situazione e spero che una di queste diventerà una versione futura di Java.


1
(1) Esistono già diverse risposte che mostrano un esempio come questo, quindi cosa aggiunge la tua risposta alle domande e risposte che non sono già coperte? La pubblicazione di risposte duplicate come questa non fa che aggiungere confusione al sito. (2) L'OP afferma specificamente che non vuole farlo. "Si prega di notare che NON voglio racchiudere l'eccezione selezionata all'interno di un'eccezione di runtime e lanciare invece l'eccezione spostata non selezionata."
Radiodef,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.