2014-09-03 2 views
44

Я работаю над проектом с 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.

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

+10

В стороне, это не выражение лямбда - это ссылка на метод. Это выражение было бы lambda, если бы вы использовали 'forEach (x -> deleteChild (x))'. Однако это пойдет по той же причине. –

ответ

49

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

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

+0

Hi Thx для вашего вопроса/Thx для вашего ответа. Как насчет того, чтобы не использовать проверенные исключения из java 8 и выше? – avi613

+0

@ avi613: Я не уверен, что вы имеете в виду, но проверенные исключения не являются необязательными ... –

+0

Конечно, это не так! :) Я читал о людях, не согласных о проверенных v.s. непроверенные исключения. См. [Пример] (http://docs.oracle.com/javase/tutorial/essential/exceptions/runtime.html). Здесь документ Oracle довольно окончательный о том, как использовать проверенные исключения. Однако они упоминают исключение, исключенное из ограничений, налагаемое на использование лямбда. Мне было интересно, может ли это ограничение быть достаточно плохим, чтобы избежать использования проверенных исключений. – avi613

9

Вы можете попробовать это:

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

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

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 метода, содержащего поток. Компилятор не заставит вас добавить его больше, поэтому его легче забыть.

• Если код вызова уже обрабатывает проверенное исключение, компилятор НАСТОЯТЕЛЬНО напоминает вам добавить предложение throws к объявлению метода , содержащему поток (если вы этого не скажете: исключение никогда не выбрасывается тело соответствующего утверждения try).

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

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

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

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

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

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

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

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

0

Вы также можете объявить 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.

Смежные вопросы