Подтвердить что ты не робот

Java 8 метод ссылки необработанное исключение

Я работаю над проектом с Java 8 и обнаружил одну ситуацию, которую я не могу понять.

У меня такой код:

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

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

Этот код работает нормально, но я могу переписать его с помощью ссылки на метод:

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

И этот код не компилируется, давая ошибку Incompatible thrown types *SomeException* in method reference.

И IDEA дал мне ошибку unhandled exception.

Итак, мой вопрос, почему? Почему код компилируется для каждого цикла и не компилируется с лямбдой?

4b9b3361

Ответ 1

Если вы посмотрите на Consumer<T>, то метод accept (который является тем, что эффективно использует ссылка на метод) 'объявлено, что выбрасывает любые проверенные исключения - поэтому вы не можете использовать ссылку на метод, которая объявлена ​​для того, чтобы выбрасывать исключенное исключение. Улучшенный цикл for в порядке, потому что там вы всегда находитесь в контексте, где SomeException можно выбросить.

Вы могли бы создать оболочку, которая преобразует проверенное исключение в исключенное исключение и бросает это. В качестве альтернативы вы можете объявить свой собственный функциональный интерфейс с помощью метода accept(), который выдает проверенное исключение (возможно, параметризует интерфейс с этим исключением), а затем записывает собственный метод forEach, который использует этот функциональный интерфейс в качестве входа.

Ответ 2

Вы можете попробовать следующее:

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

Ниже UtilException вспомогательный класс UtilException позволяет использовать любые проверенные исключения в потоках Java. Обратите внимание, что поток выше также выбрасывает исходное проверенное исключение, созданное this::deleteChild, и NOT some this::deleteChild unchecked exception.

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

}

Многие другие примеры использования (после статического импорта 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");
    }

Но не используйте его, прежде чем понимать следующие преимущества, недостатки и ограничения:

• Если вызывающий код обрабатывает исключенное исключение, вы ДОЛЖНЫ добавить его в предложение throws метода, содержащего поток. Компилятор не заставит вас добавлять его больше, поэтому его легче забыть.

• Если вызывающий код уже обрабатывает исключенное исключение, компилятор WILL напоминает вам добавить предложение throws в объявление метода, которое содержит поток (если вы этого не скажете: исключение никогда не бросается в тело соответствующего утверждения try).

• В любом случае вы не сможете окружить поток, чтобы поймать исключенное исключение. INSIDE метод, который содержит поток (если вы попытаетесь, компилятор скажет: "Исключение никогда не выбрасывается в тело соответствующей инструкции try").

• Если вы вызываете метод, который буквально никогда не может генерировать исключение, которое он объявляет, тогда вы не должны включать предложение throws. Например: new String (byteArr, "UTF-8") выдает UnsupportedEncodingException, но UTF-8 гарантируется спецификацией Java всегда присутствовать. Здесь объявление бросков является неприятным, и любое решение, чтобы заставить его замолчать с минимальным шаблоном, приветствуется.

• Если вы ненавидите проверенные исключения и чувствуете, что они никогда не должны быть добавлены к Java-языку для начала (все большее число людей думает так, и я НЕ являюсь одним из них), то просто не добавляйте проверенное исключение в бросает предложение метода, содержащего поток. Таким образом, проверенное исключение будет вести себя так же, как исключение Unchecked.

• Если вы внедряете строгий интерфейс, в котором у вас нет возможности добавить объявление бросков, и при этом исключение исключение вполне уместно, то обертывание исключения, чтобы получить привилегию бросать его, приводит к stacktrace с ложными исключениями которые не дают никакой информации о том, что на самом деле пошло не так. Хорошим примером является Runnable.run(), который не бросает никаких проверенных исключений. В этом случае вы можете не добавлять исключенное исключение в предложение throws метода, содержащего поток.

• В любом случае, если вы решите НЕ добавлять (или забывать добавить) исключенное исключение в предложение throws метода, содержащего поток, помните об этих двух последствиях бросания CHECKED исключений:

1) Код вызова не сможет поймать его по имени (если вы попытаетесь, компилятор скажет: "Исключение никогда не выбрасывается в тело соответствующей инструкции try"). Он будет пузыриться и, вероятно, попадет в основной программный цикл с помощью некоторых "исключений catch" или "catch Throwable", которые могут быть тем, что вы хотите в любом случае.

2) Это нарушает принцип наименьшего удивления: больше не хватит, чтобы поймать RuntimeException, чтобы гарантировать возможность обнаружения всех возможных исключений. По этой причине я считаю, что этого не следует делать в коде кода, но только в бизнес-коде, который вы полностью контролируете.

В заключение: я считаю, что ограничения здесь не являются серьезными, и класс UtilException может использоваться без страха. Однако, это зависит от вас!

Ответ 3

Вы также можете объявить someException, чтобы он расширил RuntimeException вместо Exception. Следующий примерный код будет скомпилирован:

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

Выход будет следующим:

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)

Вы можете добавить блок try/catch вокруг оператора forEach, однако выполнение инструкции forEach будет прервано после генерирования исключения. В приведенном выше примере элемент "bar" списка не будет напечатан. Кроме того, при этом вы потеряете отслеживаемое исключение в своей среде IDE.

Ответ 4

** Если вы не хотите писать свой собственный пользовательский интерфейс и использовать его. Вы можете легко использовать свое пользовательское исключение, как показано ниже. Вы можете выполнить, как показано ниже. **

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