2014-09-03 33 views
44

Tôi đang làm việc trên dự án với Java 8 và tìm thấy một tình huống mà tôi không thể hiểu được.Tham chiếu phương thức Java 8 không được xử lý ngoại lệ

tôi có mã như thế này:

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

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

Mã này đang làm việc tốt, nhưng tôi có thể viết lại nó với một tài liệu tham khảo phương pháp:

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

Và mã này không biên dịch, đưa ra các lỗi Incompatible thrown types *SomeException* in method reference.

IDEA cũng cho tôi lỗi unhandled exception.

Vì vậy, câu hỏi của tôi là lý do tại sao? Tại sao mã biên dịch với mỗi vòng lặp và không biên dịch với lambda?

+10

Là một sang một bên, đây không phải là một biểu thức lambda - đó là một tham chiếu phương pháp. Nó sẽ là một biểu thức lambda nếu bạn sử dụng 'forEach (x -> deleteChild (x))'. Điều đó sẽ thất bại cho cùng một lý do mặc dù. –

Trả lời

49

Nếu bạn nhìn vào giao diện Consumer<T>, các accept phương pháp (đó là những gì tham khảo phương pháp của bạn sẽ có hiệu quả được sử dụng) không được khai báo để ném bất kỳ trường hợp ngoại lệ kiểm tra - do đó bạn không thể sử dụng một tài liệu tham khảo phương pháp mà tuyên bố ném một ngoại lệ đã kiểm tra. Việc tăng cường cho vòng lặp là okay, bởi vì có bạn luôn luôn trong một bối cảnh nơi SomeException có thể được ném.

Bạn có khả năng tạo ra một trình bao bọc chuyển đổi ngoại lệ đã kiểm tra thành một ngoại lệ không được chọn và ném nó. Ngoài ra, bạn có thể khai báo giao diện chức năng của riêng mình với phương thức accept()làm ném ngoại lệ đã kiểm tra (có thể tham số hóa giao diện với ngoại lệ đó), rồi viết phương thức forEach của riêng bạn.

+0

Xin chào Thx cho câu hỏi của bạn/Thx cho câu trả lời của bạn. Điều gì về việc không sử dụng ngoại lệ đã kiểm tra từ java 8 trở lên? – avi613

+0

@ avi613: Tôi không chắc chắn ý bạn là gì, nhưng ngoại lệ được kiểm tra không phải là tùy chọn ... –

+0

Tất nhiên là không! :) Tôi đã đọc về những người không đồng ý về v.s. các ngoại lệ không được kiểm soát. Xem [ví dụ] (http://docs.oracle.com/javase/tutorial/essential/exceptions/runtime.html). Ở đây tài liệu Oracle là khá cuối cùng về cách sử dụng các ngoại lệ đã kiểm tra. Tuy nhiên, họ đề cập đến ngoại lệ kiểm tra giới hạn áp đặt lên việc sử dụng lambdas. Tôi đã tự hỏi liệu giới hạn này có thể đủ tệ để tránh sử dụng các ngoại lệ đã kiểm tra hay không. – avi613

9

Bạn có thể thử này:

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

Lớp UtilException helper bên dưới cho phép bạn sử dụng bất kỳ kiểm tra ngoại lệ trong Java suối. Lưu ý rằng luồng trên cũng ném ngoại lệ đã kiểm tra ban đầu được ném bởi this::deleteChild và KHÔNG bao gồm một số ngoại lệ được bỏ chọn.

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

} 

Nhiều ví dụ khác về cách sử dụng nó (sau khi tĩnh nhập khẩu 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"); 
    } 

Nhưng đừng sử dụng nó trước khi tìm hiểu những ưu điểm, nhược điểm và hạn chế sau:

• Nếu mã gọi là xử lý ngoại lệ đã kiểm tra, bạn phải thêm nó vào mệnh đề ném của phương thức chứa luồng. Trình biên dịch sẽ không buộc bạn thêm nó nữa, vì vậy sẽ dễ dàng quên nó hơn.

• Nếu mã gọi đã xử lý ngoại lệ đã chọn, trình biên dịch S remind nhắc bạn thêm mệnh đề ném vào khai báo phương thức có chứa luồng (nếu bạn không nói: Ngoại lệ không bao giờ được ném vào phần thân của câu lệnh try tương ứng).

• Trong mọi trường hợp, bạn sẽ không thể tự bao quanh luồng để bắt ngoại lệ được kiểm tra INSIDE phương thức chứa luồng (nếu bạn thử, trình biên dịch sẽ nói: Ngoại lệ không bao giờ được ném vào phần thân của câu lệnh thử tương ứng).

• Nếu bạn đang gọi một phương pháp có nghĩa là không bao giờ có thể ném ngoại lệ mà nó tuyên bố, thì bạn không nên bao gồm mệnh đề ném. Ví dụ: chuỗi mới (byteArr, "UTF-8") ném UnsupportedEncodingException, nhưng UTF-8 được đảm bảo bởi thông số Java để luôn có mặt. Ở đây, các tuyên bố ném là một phiền toái và bất kỳ giải pháp để im lặng nó với boilerplate tối thiểu được chào đón.

• Nếu bạn ghét các ngoại lệ đã kiểm tra và cảm thấy chúng không bao giờ được thêm vào ngôn ngữ Java để bắt đầu (số người ngày càng tăng theo cách này, và tôi KHÔNG là một trong số đó) ngoại lệ đã kiểm tra đối với điều khoản ném của phương thức chứa luồng. Sau đó, ngoại lệ được kiểm tra sẽ hoạt động giống như một ngoại lệ được kiểm tra.

• Nếu bạn đang triển khai giao diện nghiêm ngặt, nơi bạn không có tùy chọn thêm tuyên bố ném, và ném ngoại lệ là hoàn toàn thích hợp, sau đó bao gồm ngoại lệ chỉ để có được đặc quyền ném kết quả một stacktrace với ngoại lệ giả mạo mà đóng góp không có thông tin về những gì thực sự đã đi sai. Một ví dụ tốt là Runnable.run(), không ném bất kỳ ngoại lệ đã kiểm tra nào. Trong trường hợp này, bạn có thể quyết định không thêm ngoại lệ đã kiểm tra vào điều khoản ném của phương thức chứa luồng.

• Trong mọi trường hợp, nếu bạn quyết định không thêm (hoặc quên thêm) ngoại trừ kiểm tra để ném khoản của phương pháp có chứa các dòng, thể nhận thức được những 2 hậu quả của việc ném ngoại lệ CHECKED:

1) Mã gọi sẽ không thể bắt được bằng tên (nếu bạn thử, trình biên dịch sẽ nói: Ngoại lệ không bao giờ được ném vào phần thân của câu lệnh try tương ứng). Nó sẽ bong bóng và có thể được bắt trong vòng lặp chương trình chính bởi một số "bắt ngoại lệ" hoặc "bắt Throwable", mà có thể là những gì bạn muốn anyway.

2) Vi phạm nguyên tắc ít ngạc nhiên nhất: nó sẽ không còn đủ để bắt RuntimeException để có thể đảm bảo bắt tất cả ngoại lệ có thể xảy ra. Vì lý do này, tôi tin rằng điều này không nên được thực hiện trong mã khung công tác, nhưng chỉ trong mã doanh nghiệp mà bạn hoàn toàn kiểm soát.

Kết luận: Tôi tin rằng những hạn chế ở đây không nghiêm trọng và lớp học UtilException có thể được sử dụng mà không sợ hãi. Tuy nhiên, tùy thuộc vào bạn!

0

Bạn cũng có thể khai báo someException để mở rộng RuntimeException thay vì Exception. Đoạn mã ví dụ sau đây sẽ biên dịch:

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

Kết quả sau đó sẽ là:

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) 

Bạn có thể thêm một khối try/catch xung quanh tuyên bố forEach, tuy nhiên việc thực hiện báo cáo kết quả forEach sẽ bị gián đoạn khi một ngoại lệ được ném. Trong ví dụ trên, phần tử "bar" của danh sách sẽ không được in. Ngoài ra, bằng cách đó, bạn sẽ mất dấu vết của ngoại lệ được ném trong IDE của bạn.

Các vấn đề liên quan