web-dev-qa-db-fra.com

Java 8 exception non gérée de référence de méthode

Je travaille sur un projet avec Java 8 et ai trouvé une situation que je ne comprends pas.

J'ai un code comme ça:

void deleteEntity(Node node) throws SomeException {
    for (ChildNode child: node.getChildren()) {
       deleteChild(child);
    }
}

void deleteChild(Object child) throws SomeException {
    //some code
}

Ce code fonctionne bien, mais je peux le réécrire avec une référence de méthode:

void deleteEntity(Node node) throws SomeException {
    node.getChildren().forEach(this::deleteChild);
}

Et ce code ne compile pas, donnant l'erreur Incompatible thrown types *SomeException* in method reference.

Aussi IDEA m'a donné l'erreur unhandled exception.

Alors, ma question est pourquoi? Pourquoi le code compile avec pour chaque boucle et ne compile pas avec lambda?

57
Vartlok

Si vous regardez l'interface Consumer<T> , la méthode accept (ce que votre référence de méthode utiliserait effectivement) n'est pas déclarée pour renvoyer les exceptions vérifiées - par conséquent, vous ne pouvez pas utiliser une référence de méthode qui est déclarée pour générer une exception vérifiée. La boucle for améliorée est correcte, car vous êtes toujours dans un contexte où SomeException peut être lancé.

Vous pouvez éventuellement créer un wrapper qui convertit l'exception vérifiée en une exception non contrôlée et la lancer. Alternativement, vous pouvez déclarer votre propre interface fonctionnelle avec une méthode accept() qui fait une exception vérifiée (probablement en paramétrant l'interface avec celle-ci). puis écrivez votre propre méthode forEach prenant cette interface fonctionnelle en entrée.

65
Jon Skeet

Vous pouvez essayer ceci:

void deleteEntity(Node node) throws SomeException {     node.getChildren().forEach(UtilException.rethrowConsumer(this::deleteChild));
    }

La classe d'assistance UtilException ci-dessous vous permet d'utiliser toutes les exceptions vérifiées dans les flux Java. Notez que le flux ci-dessus lève également l'exception vérifiée d'origine levée par this::deleteChild, et PAS une exception non vérifiée enveloppante.

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 sur son utilisation (après avoir importé de manière statique 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");
    }

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 d'appel gère déjà l'exception vérifiée, le compilateur vous rappellera d'ajouter la clause throws à la déclaration de méthode contenant le flux (sinon, il dira: l'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 capturer l'exception cochée DANS la méthode qui contient le flux (si vous essayez, le compilateur dira: une 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) toujours présente. Dans ce cas, la déclaration des éjections 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 doivent jamais être ajoutées à la 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 l'exception vérifiée à la clause throws de la méthode qui contient le flux, elle 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 et que le lancement d'une exception est tout à fait approprié, encapsuler une exception uniquement pour obtenir le privilège de le lancer résulte en un stacktrace avec des exceptions parasites. qui ne fournissent aucune information sur ce qui a réellement mal tourné. 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 oubliez d'ajouter) l'exception vérifiée à la clause throws de la méthode contenant le flux, tenez compte de ces 2 conséquences de la projection des exceptions CHECKED:

1) Le code d'appel ne pourra pas l'attraper par son nom (si vous essayez, le compilateur dira: une 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 peut être ce que vous voulez de toute façon.

2) Cela viole le principe de la moindre surprise: il ne suffira plus d'attraper RuntimeException pour pouvoir garantir la capture de toutes les 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, à vous de choisir!

16
MarcG

Vous pouvez également déclarer someException pour qu'il étend RuntimeException au lieu de Exception. L'exemple de code suivant va compiler:

public class Test {

    public static void main(String[] args){
        // TODO Auto-generated method stub
        List<String> test = new ArrayList<String>();
        test.add("foo");
        test.add(null);
        test.add("bar");
        test.forEach(x -> print(x));    
    }

    public static class SomeException extends RuntimeException{
    }

    public static void print(String s) throws SomeException{
        if (s==null) throw new SomeException();
        System.out.println(s);
    }
}

La sortie sera alors:

foo
Exception in thread "main" simpleTextLayout.Test$SomeException
at simpleTextLayout.Test.print(Test.Java:22)
at simpleTextLayout.Test.lambda$0(Test.Java:14)
at Java.util.ArrayList.forEach(ArrayList.Java:1249)
at simpleTextLayout.Test.main(Test.Java:14)

Vous pouvez ajouter un try/catchbloquez autour de l'instruction forEach, toutefois l'exécution de l'instruction forEach sera interrompue une fois qu'une exception sera levée. Dans l'exemple ci-dessus, le "bar" L'élément de la liste ne sera pas imprimé. De plus, en faisant cela, vous perdrez la trace de l'exception levée dans votre IDE.

0
Vincz777

** Si vous ne voulez pas écrire votre propre interface client et l'utiliser. Vous pouvez facilement utiliser votre exception personnalisée comme indiqué ci-dessous. Vous pouvez effectuer comme ci-dessous. **

list.stream().forEach(x->{
try{
System.out.println(x/0);
}catch(ArithmeticException e){
throw new RuntimeException(new MyCustomException(FirstArgument,SecondArgument));
});
0