2014-09-03 17 views
44

Pracuję nad projektem z Javą 8 i znalazłem jedną sytuację, której nie mogę zrozumieć.Nieznane odwołanie do metody Java 8

mam kod jak poniżej:

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

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

Ten kod działa poprawnie, ale mogę przepisać go z odniesieniem metoda:

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

I ten kod nie skompilować, dając błąd Incompatible thrown types *SomeException* in method reference.

Również IDEA podała mi błąd unhandled exception.

Moje pytanie brzmi: dlaczego? Dlaczego kod kompiluje się dla każdej pętli i nie kompiluje się z lambda?

+10

Na marginesie to nie jest wyrażenie lambda - jest to odwołanie do metody. Byłby to wyrażenie lambda, jeśli użyjesz 'forEach (x -> deleteChild (x))'. To by jednak zawiodło z tego samego powodu. –

Odpowiedz

49

Jeśli spojrzeć na interfejsie Consumer<T> metoda accept (co jest to, co będzie referencyjna metoda efektywnego wykorzystania) nie jest uznane rzucać żadnych sprawdzane wyjątki - dlatego nie można użyć odniesienia jest metoda, która zadeklarowany, aby rzucić sprawdzony wyjątek. Ulepszona pętla for jest w porządku, ponieważ zawsze znajdujesz się w sytuacji, w której można rzucić SomeException.

Można potencjalnie utworzyć opakowanie, które przekształci sprawdzony wyjątek w niezaznaczony wyjątek i wyrzuci go. Ewentualnie można zadeklarować własny interfejs funkcjonalny za pomocą metody accept(), którą dokonuje wyrzucić sprawdzony wyjątek (prawdopodobnie parametryzując interfejs z tym wyjątkiem), a następnie napisać własną metodę forEach, która przyjmuje interfejs funkcjonalny jako dane wejściowe.

+0

Cześć Thx za twoje pytanie/Thx za odpowiedź. A może nie używać sprawdzonych wyjątków od java 8 i wyżej? – avi613

+0

@ avi613: Nie jestem pewien co masz na myśli, ale zaznaczone wyjątki nie są opcjonalne ... –

+0

Oczywiście, że nie są! :) Czytałem o ludziach, którzy nie zgadzają się co do sprawdzonych v.s. niezaznaczone wyjątki. Zobacz na [przykład] (http://docs.oracle.com/javase/tutorial/essential/exceptions/runtime.html). Tutaj dokument Oracle jest dość ostateczny, jak używać sprawdzonych wyjątków. Jednak wspominają one o ograniczonym sprawdzaniu wyjątku narzucającym stosowanie lambdas. Zastanawiam się, czy to ograniczenie może być na tyle złe, aby uniknąć używania sprawdzonych wyjątków. – avi613

9

Możesz spróbować tego:

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

Klasa UtilException pomocnik poniżej pozwala użyć dowolnego sprawdzone wyjątków w Javie strumieni. Zauważ, że powyższy strumień również zgłasza oryginalny zaznaczony wyjątek zgłoszony przez this::deleteChild, a NIE niektóre zawijanie niezaznaczonego wyjątku.

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

} 

wiele innych przykładów, w jaki sposób z niego korzystać (po statycznie importowania 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"); 
    } 

ale nie używaj go przed zrozumienia następujących zalet, wad i ograniczeń:

• Jeśli kod wywoławczy ma obsłużyć sprawdzony wyjątek, MUSISZ dodać go do klauzuli throws metody zawierającej strumień. Kompilator nie wymusi już dodania go, więc łatwiej go zapomnieć.

• Jeśli kod wywołujący już obsługuje sprawdzony wyjątek, kompilator BĘDZIE przypominał, aby dodać klauzulę rzutów do deklaracji metody , która zawiera strumień (jeśli nie, to powie: Wyjątek nigdy nie jest wrzucany treść odpowiadającej instrukcji try).

• W każdym przypadku nie będzie można otoczyć samego strumienia, aby przechwycić zaznaczony wyjątek WEWNĄTRZ metody zawierającej strumień (jeśli spróbujesz, kompilator powie: Wyjątek nigdy nie zostanie zgłoszony w treści odpowiednia instrukcja try).

• Jeśli wywołujesz metodę, która nigdy nie może rzucić wyjątku, który deklaruje, nie powinieneś zawierać klauzuli throws. Na przykład: nowy ciąg (byteArr, "UTF-8") zgłasza UnsupportedEncodingException, ale kodowanie UTF-8 jest gwarantowane przez specyfikację Java, aby zawsze była obecna. Tutaj deklaracja rzutów jest uciążliwa i mile widziane jest każde rozwiązanie, które ją uciszy przy minimalnym ustawieniu.

• Jeśli nienawidzisz sprawdzonych wyjątków i uważasz, że nigdy nie powinny być one dodawane do języka Java na początku (coraz więcej osób myśli w ten sposób, i NIE jestem jednym z nich), to po prostu nie dodawaj sprawdzony wyjątek do klauzuli throws metody zawierającej strumień. Sprawdzony wyjątek będzie zachowywał się tak jak wyjątek Nieprawidłowy.

• Jeśli implementujesz ścisły interfejs, w którym nie masz możliwości dodawania deklaracji rzutów, a mimo to wyrzucenie wyjątku jest całkowicie odpowiednie, , to zawijanie wyjątku tylko po to, aby uzyskać przywilej rzucania go, powoduje stacktrace z fałszywymi wyjątkami, które nie zawierają żadnych informacji o tym, co faktycznie było nie tak. Dobrym przykładem jest Runnable.run(), która nie wyrzuca żadnych sprawdzonych wyjątków. W takim przypadku możesz nie dodawać sprawdzanego wyjątku do klauzuli throws metody zawierającej strumień.

• W każdym razie, jeśli nie zdecydujesz się dodać (lub zapomnij dodać) sprawdzonej wyjątek rzutów klauzuli metody, która zawiera strumień, zdawać sobie sprawę z tych 2 konsekwencji rzuca Sprawdzone wyjątkami:

1) Kod wywołujący nie będzie mógł go przechwycić po nazwie (jeśli spróbujesz, kompilator powie: Wyjątek nigdy nie zostanie zgłoszony w treści odpowiadającej oświadczeniu ). Będzie bąbelkować i prawdopodobnie zostanie przechwycony w głównej pętli programu przez "wyjątek catch" lub "catch Throwable", który i tak może być potrzebny.

2) Narusza on zasadę najmniejszego zaskoczenia: nie wystarczy już uchwycić wyjątku RuntimeException, aby móc zagwarantować możliwość złapania wszystkich możliwych wyjątków od . Z tego powodu uważam, że nie powinno się tego robić w kodzie źródłowym, ale tylko w kodzie biznesowym, który całkowicie kontrolujesz.

Podsumowując: uważam, że ograniczenia tutaj nie są poważne, a klasa UtilException może być używana bez obaw. Jednak to zależy od Ciebie!

0

Możesz także zadeklarować someException tak, że rozciąga RuntimeException zamiast Exception. Poniższy przykładowy kod zostanie skompilowany:

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

Wyjście będzie wtedy:

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) 

Możesz dodać try/catch blok wokół rachunku forEach, jednak wykonanie zestawienia forEach zostanie przerwane kiedyś wyjątek został zgłoszony. W powyższym przykładzie element "bar" na liście nie zostanie wydrukowany. W ten sposób stracisz możliwość śledzenia wyjątku w swoim IDE.

Powiązane problemy