web-dev-qa-db-fra.com

Java 8: Lambda-Streams, Filtrer par méthode avec exception

J'ai un problème pour essayer les expressions Lambda de Java 8 . Habituellement, cela fonctionne bien, mais maintenant, j'ai des méthodes qui jettent IOException 's. C'est mieux si vous regardez le code suivant:

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

Le problème est que cela ne compile pas, car je dois attraper les exceptions possibles des méthodes isActive- et getNumber-Methods. Mais même si j'utilise explicitement un bloc try-catch-block comme ci-dessous, il ne compile toujours pas car je n'intercepte pas l'exception. Donc soit il y a un bug dans JDK, soit je ne sais pas comment attraper ces exceptions.

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){
        }
    }
    ....
}

Comment puis-je le faire fonctionner? Quelqu'un peut-il me suggérer la bonne solution?

149
Martin Weber

Vous devez attraper l'exception avant il échappe au lambda:

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

Considérez le fait que le lambda n’est pas évalué à l’endroit où vous l’écrivez, mais à un endroit totalement indépendant, au sein d’une classe JDK. Ce serait donc le point où cette exception vérifiée serait levée, et à cet endroit, elle n’est pas déclarée.

Vous pouvez le gérer en utilisant un wrapper de votre lambda qui traduit les exceptions vérifiées en exceptions non vérifiées:

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

Votre exemple serait écrit comme

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

Dans mes projets, je traite cette question sans envelopper; J'utilise plutôt une méthode qui désactive efficacement la vérification des exceptions par le compilateur. Inutile de dire que cela doit être manipulé avec précaution et que tout le monde sur le projet doit savoir qu’une exception vérifiée peut apparaître si elle n’est pas déclarée. C'est le code de plomberie:

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

et vous pouvez vous attendre à recevoir un IOException au visage, même si collect ne le déclare pas. Dans la plupart des cas réels, mais pas tous, vous souhaiterez simplement réexaminer l'exception, de toute façon, et la traiter comme un échec générique. Dans tous ces cas, rien n'est perdu dans la clarté ou la correction. Méfiez-vous des autres cas dans lesquels vous voudriez réellement réagir à l'exception sur-le-champ. Le compilateur ne fera pas savoir au développeur qu'il y a un IOException à intercepter et il se plaindra en fait si vous essayez de l'attraper parce que nous l'avons dupé en lui faisant croire qu'aucune exception de ce type ne peut être levée.

191
Marko Topolnik

Vous pouvez également propager votre douleur statique avec lambdas, afin que tout soit lisible:

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

propagate reçoit ici Java.util.concurrent.Callable en tant que paramètre et convertit toute exception interceptée lors de l'appel en RuntimeException. Il existe une méthode de conversion similaire Throwables # propager (Throwable) en goyave.

Cette méthode semble être essentielle pour le chaînage de la méthode lambda. J'espère donc qu'un jour elle sera ajoutée à l'une des bibliothèques populaires ou que ce comportement de propagation le sera par défaut.

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);
    }
}
26
Andrey Chaschev

Cette classe d'assistance UtilException vous permet d'utiliser toutes les exceptions vérifiées dans les flux Java, comme ceci:

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

Note Class::forName jette ClassNotFoundException, qui est vérifié. Le flux lui-même jette également ClassNotFoundException, et PAS une exception d'emballage non vérifiée.

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

}

De nombreux autres exemples d'utilisation (après avoir importé UtilException de manière statique):

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

Mais ne l'utilisez pas avant d'avoir compris les avantages, inconvénients et limitations suivants:

• Si le code appelant doit gérer l'exception vérifiée, vous DEVEZ l'ajouter à la clause throws de la méthode qui contient le flux . Le compilateur ne vous obligera plus à l'ajouter, il est donc plus facile de l'oublier.

• Si le code appelant gère déjà l'exception vérifiée, le compilateur vous rappellera d'ajouter la clause throws à la déclaration de la méthode qui contient le flux (si vous ne le faites pas, il dira: une exception n'est jamais levée dans le corps de l'instruction try correspondante).

• Dans tous les cas, vous ne pourrez pas entourer le flux lui-même pour intercepter l'exception vérifiée À L'INTÉRIEUR de la méthode qui contient le flux (Si vous essayez, le compilateur dira: L'exception n'est jamais levée dans le corps de l'instruction try correspondante).

• Si vous appelez une méthode qui ne peut littéralement jamais lever l'exception qu'elle déclare, vous ne devez pas inclure la clause throws . Par exemple: new String (byteArr, "UTF-8") lève une exception UnsupportedEncodingException, mais UTF-8 est garanti par la spécification Java pour qu'il soit toujours présent . Ici, la déclaration des lancers est une nuisance et toute solution pour la réduire au silence avec un passe-partout minimal est la bienvenue.

• Si vous détestez les exceptions vérifiées et estimez qu'elles ne devraient jamais être ajoutées au langage Java pour commencer (un nombre croissant de personnes pensent de cette façon, Et je ne suis pas l'une d'elles), alors n'ajoutez pas simplement la vérification. exception à la clause throws de la méthode contenant le flux. Le vérifié exception se comportera alors comme une exception non vérifiée.

• Si vous implémentez une interface stricte pour laquelle vous n'avez pas l'option d'ajouter une déclaration de projection, alors que le lancement d'une exception est tout à fait approprié, encapsuler une exception simplement pour avoir le privilège de le lancer aboutit à un stacktrace avec de fausses exceptions qui ne donne aucune information sur ce qui s'est réellement passé. Un bon exemple est Runnable.run (), qui ne lève aucune exception vérifiée . Dans ce cas, vous pouvez décider de ne pas ajouter l'exception vérifiée à la clause throws de la méthode contenant le flux.

• Dans tous les cas, si vous décidez de ne PAS ajouter (ou d'oublier d'ajouter) l'exception vérifiée à la clause throws de la méthode contenant le flux, soyez conscient de ces 2 conséquences de jeter des exceptions CHECKED:

1) Le code d'appel ne pourra pas l'attraper par son nom (si vous essayez, le compilateur dira: L'exception n'est jamais levée dans le corps de l'instruction try Correspondante). Il fera des bulles et sera probablement attrapé dans la boucle principale du programme par un "catch Exception" ou un "catch Throwable", qui pourrait être ce que vous avez veux quand même.

2) Cela viole le principe de la moindre surprise: il ne suffira plus d'attraper RuntimeException pour pouvoir garantir de tout attraper exceptions possibles. Pour cette raison, j'estime que cela ne devrait pas être fait dans le code cadre, mais uniquement dans le code métier que vous contrôlez complètement.

En conclusion: je crois que les limitations ici ne sont pas sérieuses et que la classe UtilException peut être utilisée sans crainte. Cependant, c'est à vous de jouer!

17
MarcG

Vous pouvez potentiellement lancer votre propre variante Stream en encapsulant votre lambda pour qu'il lève une exception non contrôlée, puis en décompressant ultérieurement cette exception non contrôlée sur les opérations de terminal:

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

Ensuite, vous pouvez utiliser ceci de la même manière que 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());

Cette solution nécessiterait pas mal de passe-partout, je vous suggère donc de jeter un coup d'œil à la bibliothèque que j'ai déjà faite qui fait exactement ce que j'ai décrit ici pour toute la classe Stream (et plus!).

8
Jeffrey

Utilisez la méthode #propagate (). Exemple d'implémentation non-goyave de Java 8 Blog par 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);
        }
    }
}
5
n0mer

Pour ajouter correctement le code de traitement IOException (à RuntimeException), votre méthode ressemblera à ceci:

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

Le problème est maintenant que la IOException devra être capturée comme une RuntimeException et reconvertie en une IOException - et cela ajoutera encore plus de code à la méthode ci-dessus.

Pourquoi utiliser Stream alors que cela peut être fait exactement comme ceci - et que la méthode jette IOException afin qu'aucun code supplémentaire ne soit nécessaire pour cela aussi:

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

Il peut être résolu par le code simple ci-dessous avec Stream et Essayez dans AbacusUtil :

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

Divulgation: Je suis le développeur de AbacusUtil.

3
user_3380739

Cela ne répond pas directement à la question (il y en a beaucoup d'autres), mais tente d'éviter le problème en premier lieu:

D'après mon expérience, la nécessité de gérer les exceptions dans une Stream (ou une autre expression lambda) vient souvent du fait que les exceptions sont déclarées comme étant levées à partir de méthodes où elles ne devraient pas l'être. Cela vient souvent du mélange de la logique métier avec les entrées et les sorties. Votre interface Account en est un exemple parfait:

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

Au lieu de jeter une IOException sur chaque getter, considérez ce modèle:

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

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

La méthode AccountReader.readAccount(…) pourrait lire un compte dans une base de données, un fichier ou autre, et générer une exception si elle échoue. Il construit un objet Account qui contient déjà toutes les valeurs, prêt à être utilisé. Comme les valeurs ont déjà été chargées par readAccount(…), les accesseurs ne lèveraient pas d'exception. Ainsi, vous pouvez les utiliser librement dans les lambdas sans qu'il soit nécessaire d'envelopper, de masquer ou de masquer les exceptions.

Bien sûr, il n’est pas toujours possible de le faire de la manière que j’ai décrite, mais c’est souvent le cas et cela conduit à un code plus propre (IMHO):

  • Meilleure séparation des préoccupations et suivant principe de responsabilité unique
  • Moins de passe-partout: vous n'avez pas à encombrer votre code avec throws IOException pour rien d'autre que pour satisfaire le compilateur
  • Traitement des erreurs: vous gérez les erreurs où elles se produisent - lors de la lecture d'un fichier ou d'une base de données - au lieu d'être au milieu de votre logique métier uniquement parce que vous souhaitez obtenir une valeur de champ.
  • Vous pourrez peut-être faire Accountimmuable et profiter de ses avantages (par exemple, sécurité des threads)
  • Vous n'avez pas besoin de "coups bas" ou de solutions de contournement pour utiliser Account dans lambdas (par exemple, dans Stream)
3
siegi

En prolongeant la solution @marcg, vous pouvez normalement lancer et attraper une exception vérifié dans les flux; c'est-à-dire, le compilateur vous demandera d'attraper/re-jeter comme vous étiez en dehors des flux !!

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

Ensuite, votre exemple s’écrirait comme suit (en ajoutant des tests pour le montrer plus clairement):

@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
    }
}
2
PaoloC

Votre exemple peut être écrit comme suit:

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 peut être prise ici https://github.com/SeregaLBN/StreamUnthrower

1
SeregaLBN

Gardant ce problème à l'esprit, j'ai développé une petite bibliothèque pour traiter les exceptions vérifiées et les lambdas. Les adaptateurs personnalisés vous permettent d’intégrer les types fonctionnels existants:

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

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

1
Grzegorz Piwowarek

Je ne vois aucun moyen de gérer une exception vérifiée dans le flux (Java -8). La seule façon d'appliquer une application est une exception cochée dans le flux et une nouvelle levée en tant qu'exception non contrôlée.

        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());
0
atul sachan

Les interfaces fonctionnelles en Java ne déclarent aucune exception cochée ou non cochée . Nous devons changer la signature des méthodes de:

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

À:

boolean isActive();
String getNumber();

Ou manipulez-le avec le bloc 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());
}

Une autre option consiste à écrire un wrapper personnalisé ou à utiliser une bibliothèque telle que ThrowingFunction . Avec cette bibliothèque, il suffit d'ajouter la dépendance à notre pom.xml:

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

Et utilisez les classes spécifiques comme ThrowingFunction, ThrowingConsumer, ThrowingPredicate, ThrowingRunnable, ThrowingSupplier.

A la fin, le code ressemble à ceci:

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

Si cela ne vous dérange pas d'utiliser des bibliothèques tierces, le cyclops-react lib d'AOL, divulgation :: je suis un contributeur, a une classe ExceptionSoftener qui peut aider ici.

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