Java 8: Lambda-Streams, filtro per metodo con eccezione


168

Ho un problema a provare le espressioni Lambda di Java 8. Di solito funziona bene, ma ora ho metodi che lanciano IOException. È meglio se guardi il seguente codice:

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

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

Il problema è che non si compila, perché devo individuare le possibili eccezioni dei metodi isActive e getNumber. Ma anche se uso esplicitamente un blocco try-catch come di seguito, non viene ancora compilato perché non rilevo l'eccezione. Quindi o c'è un bug in JDK, o non so come catturare queste eccezioni.

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

Come posso farlo funzionare? Qualcuno può suggerirmi la soluzione giusta?




4
La risposta semplice e corretta: cogliere l'eccezione all'interno della lambda.
Brian Goetz,

Risposte:


211

Devi catturare l'eccezione prima che sfugga alla lambda:

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

Considera il fatto che lambda non viene valutato nel luogo in cui lo scrivi, ma in un posto completamente non correlato, all'interno di una classe JDK. Quindi quello sarebbe il punto in cui verrebbe generata quell'eccezione controllata, e in quel punto non viene dichiarata.

Puoi gestirlo usando un wrapper del tuo lambda che traduce le eccezioni verificate in non verificate:

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

Il tuo esempio sarebbe scritto come

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

Nei miei progetti mi occupo di questo problema senza concludere; invece uso un metodo che disattiva efficacemente il controllo delle eccezioni del compilatore. Inutile dire che questo dovrebbe essere gestito con cura e tutti i membri del progetto devono essere consapevoli del fatto che un'eccezione controllata potrebbe apparire dove non è dichiarata. Questo è il codice idraulico:

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


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

e puoi aspettarti di essere IOExceptionscagliato in faccia, anche se collectnon lo dichiara. Nella maggior parte, ma non in tutti i casi della vita reale, si vorrebbe semplicemente riproporre l'eccezione e gestirla come un errore generico. In tutti questi casi, nulla si perde nella chiarezza o nella correttezza. Basta fare attenzione a quegli altri casi, in cui si vorrebbe effettivamente reagire all'eccezione sul posto. Lo sviluppatore non verrà reso consapevole dal compilatore che esiste un modo IOExceptionper catturarlo e in realtà il compilatore si lamenterà se si tenta di catturarlo perché lo abbiamo ingannato nel credere che tale eccezione non possa essere lanciata.


4
Ho già visto NettyIO fare il "tiro subdolo" prima e volevo buttare la sedia fuori dalla finestra. "Cosa? Da dove viene verificata la perdita dell'eccezione controllata?" Questo è il primo caso d'uso legittimo per il tiro subdolo che ho ancora visto. Come programmatore devi essere vigile nell'indicare che è possibile lanciare in modo subdolo. Forse meglio è solo creare un'altra interfaccia stream / impl che supporti l'eccezione controllata?
kevinarpe,

8
Nessuna API sana dovrebbe fornire eccezioni verificate non dichiarate al client, questo è certo. All'interno dell'API può esserci una comprensione, tuttavia, che può verificarsi un'eccezione controllata. Non causano danni finché sono solo un altro segno di fallimento generale e stanno per essere catturati e gestiti all'ingrosso.
Marko Topolnik,

5
@kevinarpe Questa è la ragione esatta per cui i tiri subdoli sono una cattiva idea. Il cortocircuito del compilatore è destinato a confondere i futuri manutentori.
Thorbjørn Ravn Andersen,

29
Solo perché non ti piacciono le regole, non significa che sia una buona idea prendere la legge nelle tue mani. Il tuo consiglio è irresponsabile perché pone la convenienza dello scrittore di codice sulle considerazioni molto più importanti di trasparenza e manutenibilità del programma.
Brian Goetz,

34
@brian Solo perché qualcosa è una regola non significa che sia una buona idea. Ma sono sorpreso che tu ti riferisca alla seconda parte della mia risposta come "consiglio" poiché ho pensato di rendere palesemente ovvio ciò che propongo come soluzione e ciò che offro come un FYI al lettore interessato, con profonde dichiarazioni di non responsabilità.
Marko Topolnik,

29

Puoi anche propagare il tuo dolore statico con lambda, quindi tutto sembra leggibile:

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

propagatequi riceve java.util.concurrent.Callablecome parametro e converte qualsiasi eccezione rilevata durante la chiamata RuntimeException. Esiste un metodo di conversione simile Throwables # propagate (Throwable) in Guava.

Questo metodo sembra essere essenziale per il concatenamento del metodo lambda, quindi spero che un giorno verrà aggiunto a una delle librerie popolari o questo comportamento di propagazione sarebbe di default.

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

        return new RuntimeException(e);
    }

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

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

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


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

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

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

22

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

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

public final class UtilException {

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

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

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

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

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

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

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

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

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

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

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

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

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

}

Molti altri esempi su come usarlo (dopo l'importazione statica UtilException):

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

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

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

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

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

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

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

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

Ma non utilizzarlo prima di aver compreso i seguenti vantaggi, svantaggi e limitazioni :

• Se il codice chiamante deve gestire l'eccezione selezionata, è NECESSARIO aggiungerlo alla clausola dei tiri del metodo che contiene il flusso. Il compilatore non ti costringerà più ad aggiungerlo, quindi è più facile dimenticarlo.

• Se il codice chiamante gestisce già l'eccezione selezionata, il compilatore ricorderà di aggiungere la clausola plausibili alla dichiarazione del metodo che contiene lo stream (in caso contrario, verrà indicato: l'eccezione non viene mai generata nel corpo dell'istruzione try corrispondente ).

• In ogni caso, non sarai in grado di circondare il flusso stesso per catturare l'eccezione selezionata INSIDE il metodo che contiene il flusso (se provi, il compilatore dirà: L'eccezione non viene mai generata nel corpo dell'istruzione try corrispondente).

• Se si chiama un metodo che letteralmente non può mai generare l'eccezione dichiarata, non è necessario includere la clausola di lancio. 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.

• Se odi le eccezioni verificate e ritieni che non dovrebbero mai essere aggiunte al linguaggio Java (un numero crescente di persone la pensa in questo modo, e io NON sono una di queste), non aggiungere l'eccezione controllata al genera la clausola del metodo che contiene il flusso. L'eccezione selezionata si comporterà quindi come un'eccezione NON selezionata.

• Se stai implementando un'interfaccia rigorosa in cui non hai la possibilità di aggiungere una dichiarazione di lancio, e tuttavia lanciare un'eccezione è del tutto appropriata, quindi 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 questo caso, è possibile decidere di non aggiungere l'eccezione selezionata alla clausola throws del metodo che contiene il flusso.

• In ogni caso, se decidete di NON aggiungere (o dimenticate di aggiungere) l'eccezione verificata alla clausola dei tiri del metodo che contiene il flusso, tenete presente queste 2 conseguenze del lancio di eccezioni VERIFICATE:

1) Il codice chiamante non sarà in grado di prenderlo per nome (se si tenta, il compilatore dirà: l'eccezione non viene mai generata nel corpo della corrispondente istruzione try). 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 RuntimeException per essere in grado di 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.

In conclusione: credo che i limiti qui non siano gravi e che la UtilExceptionclasse possa essere usata senza paura. Tuttavia, dipende da te!


8

Puoi potenzialmente lanciare la tua Streamvariante avvolgendo la tua lambda per generare un'eccezione non selezionata e successivamente scartando quell'eccezione non selezionata sulle operazioni del terminale:

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

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

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

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

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

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

    // etc
}

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

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

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

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

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

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

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

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

Quindi potresti usarlo nello stesso modo esatto di un Stream:

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

Questa soluzione richiederebbe un bel po 'di boilerplate, quindi ti suggerisco di dare un'occhiata alla libreria che ho già realizzato che fa esattamente quello che ho descritto qui per l'intera Streamclasse (e altro!).


Ciao ... piccolo bug? new StreamBridge<>(ts, IOException.class);->new StreamBridge<>(s, IOException.class);
kevinarpe,

1
@kevinarpe Yep. Inoltre avrebbe dovuto dirlo StreamAdapter.
Jeffrey,

5

Usa il metodo #propagate (). Esempio di implementazione non Guava dal blog Java 8 di Sam Beran :

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

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

    public static <T, E extends Throwable> T propagate(Callable<T> callable, ExceptionWrapper<E> wrapper) throws E {
        try {
            return callable.call();
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw wrapper.wrap(e);
        }
    }
}

Il link al blog di Java 8 è morto.
Spycho,

4

Questo non risponde direttamente alla domanda (ci sono molte altre risposte che lo fanno) ma cerca di evitare il problema in primo luogo:

Nella mia esperienza, la necessità di gestire le eccezioni in Streamun'espressione (o altra espressione lambda) spesso deriva dal fatto che le eccezioni sono dichiarate generate da metodi in cui non dovrebbero essere lanciate. Questo spesso deriva dal mescolare la logica di business con input e output. La tua Accountinterfaccia è un esempio perfetto:

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

Invece di lanciare un IOExceptionsu ogni getter, considera questo disegno:

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

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

Il metodo AccountReader.readAccount(…)potrebbe leggere un account da un database o un file o qualsiasi altra cosa e generare un'eccezione se non riesce. Costruisce un Accountoggetto che contiene già tutti i valori, pronto per essere utilizzato. Poiché i valori sono già stati caricati da readAccount(…), i getter non genererebbero un'eccezione. Quindi puoi usarli liberamente in lambda senza la necessità di avvolgere, mascherare o nascondere le eccezioni.

Ovviamente non è sempre possibile farlo come ho descritto, ma spesso lo è e porta a un codice più pulito (IMHO):

  • Migliore separazione delle preoccupazioni e rispetto del principio della responsabilità unica
  • Meno boilerplate: non devi ingombrare il tuo codice throws IOExceptionper non servirti ma per soddisfare il compilatore
  • Gestione degli errori: gestisci gli errori in cui si verificano - durante la lettura da un file o database - anziché da qualche parte nel mezzo della tua logica aziendale solo perché desideri ottenere un valore di campi
  • Potresti essere in grado di rendere Account immutabile e trarre vantaggio dai suoi vantaggi (ad es. Sicurezza del thread)
  • Non hai bisogno di "trucchi sporchi" o soluzioni alternative da usare Accountin lambdas (ad esempio in a Stream)

4

Può essere risolto dal seguente codice semplice con Stream e Try in AbacusUtil :

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

Divulgazione : Sono lo sviluppatore di AbacusUtil.


3

Estendendo la soluzione @marcg, puoi normalmente lanciare e catturare un'eccezione controllata in Stream; cioè, il compilatore ti chiederà di catturare / rilanciare come se fossi al di fuori dei flussi !!

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

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

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

Quindi, il tuo esempio verrebbe scritto come segue (aggiungendo test per mostrarlo più chiaramente):

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

private class MyTestException extends Exception { }

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

@Test
public void testPredicateRaisingException() throws MyTestException {
    try {
        Stream.of("ciao", null)
                .filter(rethrowPredicate(s -> notEmpty(s)))
                .collect(toList());
        fail();
    } catch (MyTestException e) {
        //OK
    }
}

Questo esempio non viene compilato
Roman M

Ciao @RomanM, grazie per averlo indicato: ho corretto il tipo di ritorno mancante sul metodo "throwActualException". Lo stiamo usando in produzione, quindi spero che funzioni anche dalla tua parte.
PaoloC,

3

Per aggiungere correttamente il codice di gestione IOException (a RuntimeException), il metodo sarà simile al seguente:

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

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

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

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

Il problema ora è che IOExceptiondovranno essere catturati come RuntimeExceptione convertiti nuovamente in un IOException- e questo aggiungerà ancora più codice al metodo sopra.

Perché usarlo Streamquando può essere fatto proprio così - e il metodo lancia IOExceptionquindi non è necessario alcun codice aggiuntivo anche per quello:

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

1

Tenendo presente questo problema, ho sviluppato una piccola libreria per gestire le eccezioni verificate e le lambda. Gli adattatori personalizzati consentono di integrarsi con tipi funzionali esistenti:

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

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


1

Il tuo esempio può essere scritto come:

import utils.stream.Unthrow;

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

La classe Unthrow può essere presa qui https://github.com/SeregaLBN/StreamUnthrower


0

Se non ti dispiace usare le librerie di terze parti, la libreria di reazione al ciclope di AOL , divulgazione :: Sono un collaboratore, ha una classe ExceptionSoftener che può aiutarti qui.

 s.filter(softenPredicate(a->a.isActive()));

0

Le interfacce funzionali in Java non dichiarano alcuna eccezione verificata o non selezionata. Dobbiamo cambiare la firma dei metodi da:

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

Per:

boolean isActive();
String getNumber();

Oppure gestiscilo con il blocco try-catch:

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

Un'altra opzione è quella di scrivere un wrapper personalizzato o utilizzare una libreria come ThrowingFunction. Con la libreria dobbiamo solo aggiungere la dipendenza al nostro pom.xml:

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

E usa le classi specifiche come ThrowingFunction, ThrowingConsumer, ThrowingPredicate, ThrowingRunnable, ThrowingSupplier.

Alla fine il codice è simile al seguente:

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

0

Non vedo alcun modo di gestire l'eccezione verificata nello stream (Java -8), l'unico modo che ho applicato è intercettare l'eccezione controllata nello stream e rilanciarli come eccezione non selezionata.

        Arrays.stream(VERSIONS)
        .map(version -> TemplateStore.class
                .getClassLoader().getResourceAsStream(String.format(TEMPLATE_FILE_MASK, version)))
        .map(inputStream -> {
            try {
                return ((EdiTemplates) JAXBContext.newInstance(EdiTemplates.class).createUnmarshaller()
                        .unmarshal(inputStream)).getMessageTemplate();
            } catch (JAXBException e) {
                throw new IllegalArgumentException(ERROR, e);
            }})
        .flatMap(Collection::stream)
        .collect(Collectors.toList());

Stai davvero cercando di rispondere alla domanda?
Nilambar Sharma,

@Nilambar - Quello che sto cercando di dire qui è che non c'è modo di gestire l'eccezione verificata durante l'utilizzo di java stream ... alla fine di tutto ciò di cui abbiamo bisogno per catturare quella selezionata e lanciare runtime / deselezionata .. Ora ci sono due cose, 1. se pensi che non sia corretto con la mia comprensione, per favore correggimi O 2. se pensi, il mio post è irrilevante, sono felice di rimuovere lo stesso. saluti, Atul
atul sachan

0

Se vuoi gestire l'eccezione all'interno dello stream e continuare a elaborare quelli aggiuntivi, c'è un eccellente articolo in DZone di Brian Vermeer che usa il concetto di Either. Mostra un modo eccellente di gestire questa situazione. L'unica cosa che manca è il codice di esempio. Questo è un esempio della mia esplorazione usando i concetti di quell'articolo.

@Test
public void whenValuePrinted_thenPrintValue() {

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

private Object doSomething(Integer item) throws Exception {

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

    return item;
}
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.