Làm cách nào tôi có thể ném các ngoại lệ CHECKED từ bên trong các luồng Java 8?


287

Làm cách nào tôi có thể ném các ngoại lệ CHECKED từ bên trong luồng 8 luồng / lambdas?

Nói cách khác, tôi muốn tạo mã như trình biên dịch này:

public List<Class> getClasses() throws ClassNotFoundException {     

    List<Class> classes = 
        Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
              .map(className -> Class.forName(className))
              .collect(Collectors.toList());                  
    return classes;
    }

Mã này không biên dịch, vì Class.forName()phương thức trên ném ClassNotFoundException, được kiểm tra.

Xin lưu ý rằng tôi KHÔNG muốn bọc ngoại lệ được kiểm tra trong một ngoại lệ thời gian chạy và thay vào đó ném ngoại lệ không được kiểm tra. Tôi muốn tự ném ngoại lệ đã kiểm tra và không thêm xấu xí try/ catchesvào luồng.


42
Câu trả lời ngắn gọn là, bạn không thể, mà không vi phạm các quy tắc liên quan đến ngoại lệ. Bạn có thể gian lận, tất nhiên, và những người khác sẽ sẵn lòng chỉ cho bạn cách, nhưng lưu ý rằng đây là gian lận và gian lận như vậy thường quay lại cắn bạn. Bạn nên nắm bắt ngoại lệ và đối phó với nó. Nếu bạn muốn bọc nó, và sau đó lấy lại ngoại lệ đã kiểm tra, bạn có thể làm điều đó một cách an toàn.
Brian Goetz

35
@Brian, tôi không cần người khác chỉ cho tôi cách gian lận, tôi biết cách tự lừa dối bản thân và đăng cách gian lận của mình trong câu trả lời dưới đây, mà bạn đã đánh giá thấp. Tôi biết bạn tham gia vào cuộc thảo luận Java đã quyết định rằng không có cách nào tốt để xử lý các ngoại lệ được kiểm tra trong Luồng, vì vậy tôi thấy thật ngạc nhiên khi bạn nhận thấy câu hỏi này của tôi, nhưng tôi thất vọng vì câu trả lời của bạn chỉ nói "đây là không tốt ", không đưa ra lý do tại sao, và sau đó lại thêm một lần thử / bắt lại.
MarcG

22
@Brian, Thành thật mà nói, khi mọi người cố gắng cấu trúc lại các báo cáo kế thừa, một nửa trong số họ được chuyển đổi thành luồng nhưng nửa còn lại họ từ bỏ tái cấu trúc, vì không ai muốn thêm các lần thử / bắt này. Chúng khiến mã khó đọc hơn nhiều, chắc chắn là nhiều hơn so với các câu lệnh gốc. Trong ví dụ mã của tôi ở trên, miễn là bạn duy trì "ném ClassNotFoundException", tôi không thấy bất kỳ sự khác biệt nào với mã bên ngoài. Bạn có thể vui lòng cho tôi một số ví dụ thực tế trong đó điều này phá vỡ các quy tắc liên quan đến ngoại lệ?
MarcG

10
Viết các phương thức trình bao bọc cho các ngoại lệ không được kiểm tra sẽ giải quyết sự phản đối "mã lộn xộn" và không phá vỡ hệ thống loại. Câu trả lời ở đây là dùng đến một "cú ném lén lút" của một ngoại lệ được kiểm tra sẽ phá vỡ hệ thống loại, bởi vì mã gọi sẽ không mong đợi (cũng không được phép bắt) ngoại lệ được kiểm tra.
Brian Goetz

14
Nó không giải quyết sự phản đối lộn xộn mã bởi vì sau đó bạn cần thử / bắt lần thứ hai xung quanh luồng, để mở khóa và lấy lại ngoại lệ ban đầu. Ngược lại, nếu bạn ném ngoại lệ được kiểm tra, bạn chỉ cần giữ nguyên throws ClassNotFoundExceptionkhai báo phương thức có chứa luồng, để mã cuộc gọi sẽ mong đợi và được phép bắt ngoại lệ được kiểm tra.
MarcG

Câu trả lời:


250

Câu trả lời đơn giản cho câu hỏi của bạn là: Bạn không thể, ít nhất là không trực tiếp. Và đó không phải là lỗi của bạn. Oracle đã làm nó rối tung lên. Họ bám vào khái niệm ngoại lệ được kiểm tra, nhưng nhất quán quên chăm sóc các ngoại lệ được kiểm tra khi thiết kế giao diện chức năng, luồng, lambda, v.v ... Đó là tất cả các chuyên gia như Robert C. Martin, người đã gọi ngoại lệ được kiểm tra là một thử nghiệm thất bại.

Theo tôi, đây là một lỗi rất lớn trong API và một lỗi nhỏ trong đặc tả ngôn ngữ .

Lỗi trong API là nó không cung cấp phương tiện để chuyển tiếp các ngoại lệ được kiểm tra trong đó điều này thực sự sẽ có ý nghĩa rất lớn đối với lập trình chức năng. Như tôi sẽ trình bày dưới đây, một cơ sở như vậy sẽ dễ dàng có thể.

Lỗi trong đặc tả ngôn ngữ là nó không cho phép tham số loại suy ra danh sách các loại thay vì một loại miễn là tham số loại chỉ được sử dụng trong các tình huống trong đó danh sách các loại được cho phép ( throwsmệnh đề).

Kỳ vọng của chúng tôi là các lập trình viên Java là đoạn mã sau sẽ biên dịch:

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class CheckedStream {
    // List variant to demonstrate what we actually had before refactoring.
    public List<Class> getClasses(final List<String> names) throws ClassNotFoundException {
        final List<Class> classes = new ArrayList<>();
        for (final String name : names)
            classes.add(Class.forName(name));
        return classes;
    }

    // The Stream function which we want to compile.
    public Stream<Class> getClasses(final Stream<String> names) throws ClassNotFoundException {
        return names.map(Class::forName);
    }
}

Tuy nhiên, nó mang lại:

cher@armor1:~/playground/Java/checkedStream$ javac CheckedStream.java 
CheckedStream.java:13: error: incompatible thrown types ClassNotFoundException in method reference
        return names.map(Class::forName);
                         ^
1 error

Cách thức mà các giao diện chức năng được xác định hiện đang ngăn Trình biên dịch chuyển tiếp ngoại lệ - không có tuyên bố nào cho biết Stream.map()nếu như Function.apply() throws E, Stream.map() throws Ecũng vậy.

Điều còn thiếu là khai báo một tham số loại để vượt qua các ngoại lệ được kiểm tra. Đoạn mã sau đây cho thấy cách tham số kiểu truyền qua như vậy thực sự có thể được khai báo với cú pháp hiện tại. Ngoại trừ trường hợp đặc biệt trong dòng được đánh dấu, là giới hạn được thảo luận dưới đây, mã này sẽ biên dịch và hoạt động như mong đợi.

import java.io.IOException;
interface Function<T, R, E extends Throwable> {
    // Declare you throw E, whatever that is.
    R apply(T t) throws E;
}   

interface Stream<T> {
    // Pass through E, whatever mapper defined for E.
    <R, E extends Throwable> Stream<R> map(Function<? super T, ? extends R, E> mapper) throws E;
}   

class Main {
    public static void main(final String... args) throws ClassNotFoundException {
        final Stream<String> s = null;

        // Works: E is ClassNotFoundException.
        s.map(Class::forName);

        // Works: E is RuntimeException (probably).
        s.map(Main::convertClass);

        // Works: E is ClassNotFoundException.
        s.map(Main::throwSome);

        // Doesn't work: E is Exception.
        s.map(Main::throwSomeMore);  // error: unreported exception Exception; must be caught or declared to be thrown
    }   

    public static Class convertClass(final String s) {
        return Main.class;
    }   

    static class FooException extends ClassNotFoundException {}

    static class BarException extends ClassNotFoundException {}

    public static Class throwSome(final String s) throws FooException, BarException {
        throw new FooException();
    }   

    public static Class throwSomeMore(final String s) throws ClassNotFoundException, IOException  {
        throw new FooException();
    }   
}   

Trong trường hợp throwSomeMorechúng tôi muốn thấy IOExceptionbị bỏ lỡ, nhưng nó thực sự bỏ lỡ Exception.

Điều này là không hoàn hảo bởi vì suy luận kiểu dường như đang tìm kiếm một loại duy nhất, ngay cả trong trường hợp ngoại lệ. Bởi vì những suy luận kiểu cần một loại duy nhất, Ecần phải quyết tâm đến một chung supercủa ClassNotFoundExceptionIOException, đó là Exception.

Cần phải điều chỉnh định nghĩa suy luận kiểu để trình biên dịch sẽ tìm nhiều loại nếu tham số loại được sử dụng trong đó danh sách các loại được cho phép ( throwsmệnh đề). Sau đó, loại ngoại lệ được trình biên dịch báo cáo sẽ cụ thể như throwskhai báo ban đầu về các ngoại lệ được kiểm tra của phương thức được tham chiếu, không phải là một loại siêu bắt tất cả.

Tin xấu là điều này có nghĩa là Oracle đã làm nó rối tung lên. Chắc chắn họ sẽ không phá vỡ mã đất của người dùng, nhưng việc đưa các tham số loại ngoại lệ vào các giao diện chức năng hiện có sẽ phá vỡ việc biên dịch tất cả mã đất của người dùng sử dụng các giao diện này một cách rõ ràng. Họ sẽ phải phát minh ra một số đường cú pháp mới để khắc phục điều này.

Tin tức thậm chí còn tồi tệ hơn là chủ đề này đã được thảo luận bởi Brian Goetz vào năm 2010 https://bloss.oracle.com/briangoetz/entry/exception_trans minh_in_java (liên kết mới: http://mail.openjdk.java.net/pipermail/lambda -dev / 2010 -6 / 001484.html ) nhưng tôi được thông báo rằng cuộc điều tra này cuối cùng đã không được thực hiện và rằng không có công việc hiện tại nào ở Oracle mà tôi biết để giảm thiểu sự tương tác giữa các ngoại lệ được kiểm tra và lambdas.


16
Hấp dẫn. Tôi tin rằng một số người đánh giá cao các luồng cho phép mã song song dễ dàng hơn, trong khi những người khác cho phép mã sạch hơn. Brian Goetz rõ ràng quan tâm nhiều hơn đến tính song song (vì ông là tác giả của Java Concurrency in Practice), trong khi Robert Martin quan tâm nhiều hơn về mã sạch (vì ông là tác giả của cuốn sách Clean Code). Thử / bắt nồi hơi là một cái giá nhỏ để trả cho sự song song, vì vậy không có gì lạ khi Brian Goetz không kinh hoàng trước những vấn đề của việc sử dụng các ngoại lệ được kiểm tra trong các luồng. Cũng không có gì lạ khi Robert Martin ghét các trường hợp ngoại lệ được kiểm tra vì chúng thêm vào sự lộn xộn.
MarcG

5
Tôi dự đoán rằng, trong một vài năm, khó khăn trong việc xử lý các ngoại lệ được kiểm tra bên trong các luồng sẽ dẫn đến một trong hai kết quả sau: Mọi người sẽ ngừng sử dụng các ngoại lệ được kiểm tra, HOẶC mọi người sẽ bắt đầu sử dụng một số hack rất giống như tôi đã đăng câu trả lời UtilException của tôi. Tôi đã đặt cược các luồng Java-8 là cái đinh cuối cùng trên quan tài của các ngoại lệ được kiểm tra, không phải vì thực tế là các ngoại lệ được kiểm tra là một phần của JDK. Mặc dù tôi thích và sử dụng các ngoại lệ được kiểm tra trong mã doanh nghiệp (đối với một số trường hợp sử dụng cụ thể), tôi sẽ thích tất cả các ngoại lệ JDK phổ biến Thời gian chạy mở rộng.
MarcG

9
@Unihedro Vấn đề vẫn là các giao diện chức năng không chuyển tiếp ngoại lệ. Tôi sẽ cần try-catchkhối bên trong lambda, và điều đó đơn giản là không có ý nghĩa gì. Ngay khi Class.forNameđược sử dụng theo một cách nào đó trong lambda, ví dụ như trong names.forEach(Class::forName), vấn đề là ở đó. Về cơ bản, các phương pháp ném ngoại lệ được kiểm tra đã được loại trừ khỏi việc tham gia lập trình chức năng như giao diện chức năng trực tiếp, theo thiết kế (kém!).
Christian Hujer

26
@ChristianHujer Cuộc thăm dò "Minh bạch ngoại lệ" chỉ là như vậy - một cuộc thăm dò (bắt nguồn từ đề xuất BGGA). Khi phân tích sâu hơn, chúng tôi thấy rằng nó cung cấp một sự cân bằng kém về giá trị và độ phức tạp, và nó có một số vấn đề nghiêm trọng (dẫn đến các vấn đề suy luận không thể giải quyết được, và "bắt X" là không rõ ràng, trong số những người khác. có vẻ đầy hứa hẹn - thậm chí là "hiển nhiên" - nhưng sau khi khám phá sâu hơn, hóa ra là thiếu sót. Đây là một trong những trường hợp đó.
Brian Goetz

13
@BrianGoetz Có một số thông tin công khai về các vấn đề suy luận không thể giải quyết được mà bạn đề cập? Tôi tò mò và muốn hiểu nó.
Christian Hujer

169

Lớp trình LambdaExceptionUtiltrợ giúp này cho phép bạn sử dụng bất kỳ ngoại lệ được kiểm tra nào trong các luồng Java, như thế này:

Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
      .map(rethrowFunction(Class::forName))
      .collect(Collectors.toList());

Lưu ý Class::forNameném ClassNotFoundException, được kiểm tra . Luồng cũng tự ném ClassNotFoundExceptionvà KHÔNG một số ngoại lệ không được kiểm soát.

public final class LambdaExceptionUtil {

@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 nhập tĩnh LambdaExceptionUtil):

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

CHÚ THÍCH 1: Các rethrowphương thức của LambdaExceptionUtillớp trên có thể được sử dụng mà không sợ và có thể sử dụng trong mọi tình huống . Xin chân thành cảm ơn người dùng @PaoloC đã giúp giải quyết vấn đề cuối cùng: Bây giờ trình biên dịch sẽ yêu cầu bạn thêm các mệnh đề ném và mọi thứ như thể bạn có thể ném ngoại lệ được kiểm tra một cách tự nhiên trên các luồng Java 8.


CHÚ THÍCH 2: Các uncheckphương thức của LambdaExceptionUtillớp ở trên là các phương thức thưởng và có thể được gỡ bỏ chúng khỏi lớp một cách an toàn nếu bạn không muốn sử dụng chúng. Nếu bạn đã sử dụng chúng, hãy cẩn thận và không hiểu các trường hợp sử dụng sau đây, ưu điểm / nhược điểm và hạn chế:

• Bạn có thể sử dụng các uncheckphương thức nếu bạn đang gọi một phương thức mà theo nghĩa đen không bao giờ có thể ném ngoại lệ mà nó tuyên bố. Ví dụ: Chuỗi mới (byteArr, "UTF-8") ném UnsupportedEncodingException, nhưng UTF-8 được đảm bảo bởi đặc tả Java luôn luôn có mặt. Ở đây, tuyên bố ném là một phiền toái và bất kỳ giải pháp nào để làm im lặng nó với bản tóm tắt tối thiểu đều được chào đón:String text = uncheck(() -> new String(byteArr, "UTF-8"));

• Bạn có thể sử dụng các uncheckphương pháp nếu bạn đang triển khai một giao diện nghiêm ngặt mà bạn không có tùy chọn để thêm một tuyên bố ném, và ném một ngoại lệ là hoàn toàn phù hợp. Kết thúc một ngoại lệ chỉ để đạt được đặc quyền ném nó dẫn đến một stacktrace với các ngoại lệ giả mà không có thông tin về những gì thực sự đã sai. Một ví dụ điển hình là Runnable.run (), không đưa ra bất kỳ ngoại lệ nào được kiểm tra.

• Trong mọi trường hợp, nếu bạn quyết định sử dụng các uncheckphương thức, hãy lưu ý đến 2 hậu quả của việc ném ngoại lệ CHECKED mà không có mệnh đề ném: 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 trong phần thân của câu lệnh thử tương ứng). Nó sẽ nổi bong bóng và có thể bị 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 Ném được", đây có thể là những gì bạn muốn. 2) Nó vi phạm nguyên tắc ít gây bất ngờ nhất: nó sẽ không còn đủ để bắt RuntimeExceptionđể có thể đảm bảo bắt được tất cả các ngoại lệ có thể. 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, mà chỉ trong mã doanh nghiệp mà bạn hoàn toàn kiểm soát.


4
Tôi cảm thấy câu trả lời này đã bị hạ bệ một cách bất công. Các mã hoạt động. Các trường hợp ngoại lệ được kiểm tra được cho là được ném hoặc xử lý. Nếu bạn muốn ném chúng, chỉ cần giữ "mệnh đề ném" trong phương thức chứa luồng. Nhưng nếu bạn muốn đối phó với chúng bằng cách đơn giản là gói và suy nghĩ lại, tôi đoán tôi thích sử dụng mã ở trên để "giải mã" các ngoại lệ và để chúng tự bong bóng. Sự khác biệt duy nhất tôi biết là ngoại lệ sủi bọt sẽ không mở rộng RuntimeException. Tôi biết những người theo chủ nghĩa thuần túy sẽ không như vậy, nhưng liệu điều này "chắc chắn sẽ quay trở lại để cắn ai đó"? Có vẻ như không có khả năng.
MarcG

4
@Christian Hujer, thành thật với downvoter, anh ấy đã đánh giá thấp phiên bản trước đó trước khi tôi thêm giải thích "ưu điểm, nhược điểm và hạn chế". Vì vậy, có lẽ nó đã xứng đáng vào thời điểm đó. Bạn không thể dạy ai đó cách phá vỡ các quy tắc mà ít nhất là cố gắng hiểu và giải thích hậu quả. Lý do chính tại sao tôi đăng câu hỏi này là để nhận phản hồi cho những bất lợi của câu trả lời của tôi. Cuối cùng tôi đã nhận được phản hồi này không phải ở đây, mà từ một câu hỏi khác trong lập trình viên.stackexchange. Sau đó tôi trở lại đây và cập nhật câu trả lời của tôi.
MarcG

16
Tôi chỉ đánh giá thấp bởi vì điều này khuyến khích mã không thể nhầm lẫn . Đây là một hack xấu xí, mặc dù thông minh và tôi sẽ không bao giờ thấy câu trả lời này hữu ích. Đây là, một lần nữa, một "không sử dụng" của ngôn ngữ.
Unihedron

12
@Unihedro nhưng tại sao nó trở nên không thể nhầm lẫn? Tôi không thể hiểu tại sao. Ví dụ nào?
MarcG

2
Theo tôi @SuppressWarnings ("unchecked")thủ thuật biên dịch là hoàn toàn không thể chấp nhận được.
Thorbjørn Ravn Andersen

26

Bạn không thể làm điều này một cách an toàn. Bạn có thể gian lận, nhưng sau đó chương trình của bạn bị hỏng và điều này chắc chắn sẽ quay trở lại để cắn một ai đó (nó nên là bạn, nhưng thường thì sự gian lận của chúng tôi thổi vào người khác.)

Đây là một cách an toàn hơn để làm điều đó (nhưng tôi vẫn không khuyến nghị điều này.)

class WrappedException extends RuntimeException {
    Throwable cause;

    WrappedException(Throwable cause) { this.cause = cause; }
}

static WrappedException throwWrapped(Throwable t) {
    throw new WrappedException(t);
}

try 
    source.stream()
          .filter(e -> { ... try { ... } catch (IOException e) { throwWrapped(e); } ... })
          ...
}
catch (WrappedException w) {
    throw (IOException) w.cause;
}

Ở đây, những gì bạn đang làm là bắt ngoại lệ trong lambda, ném tín hiệu ra khỏi đường truyền luồng cho thấy việc tính toán không thành công, bắt tín hiệu và hành động theo tín hiệu đó để ném ngoại lệ cơ bản. Điều quan trọng là bạn luôn nắm bắt được ngoại lệ tổng hợp, thay vì cho phép một ngoại lệ được kiểm tra rò rỉ ra ngoài mà không tuyên bố rằng ngoại lệ đó được ném ra.


18
Chỉ là một câu hỏi; quyết định thiết kế dẫn đến lambdas không thể tuyên truyền các ngoại lệ được kiểm tra ra khỏi bối cảnh của họ là gì? Lưu ý rằng tôi hiểu rằng các giao diện chức năng như Functionvv không có throwsgì; Tôi chỉ tò mò thôi.
fge

4
Điều đó throw w.cause;sẽ không làm cho trình biên dịch phàn nàn rằng phương thức không ném cũng không bắt được Throwable? Vì vậy, có khả năng là một diễn viên IOExceptionsẽ cần thiết ở đó. Hơn nữa, nếu lambda ném nhiều hơn một loại ngoại lệ được kiểm tra, cơ thể của sản phẩm khai thác sẽ trở nên hơi xấu với một số instanceofkiểm tra (hoặc một cái gì đó khác có mục đích tương tự) để xác minh xem ngoại lệ được kiểm tra nào được ném.
Victor Stafusa

10
@schatten Một lý do là bạn có thể quên bắt WE, và sau đó một ngoại lệ kỳ lạ (mà không ai biết cách xử lý) sẽ bị rò rỉ. (Bạn có thể nói "nhưng bạn đã bắt được ngoại lệ, vì vậy nó an toàn." Trong ví dụ về đồ chơi này. Nhưng mỗi lần tôi thấy một cơ sở mã hóa áp dụng phương pháp này, cuối cùng cũng có người quên. Sự cám dỗ để bỏ qua các ngoại lệ không có giới hạn.) là việc sử dụng nó một cách an toàn là cụ thể cho một kết hợp cụ thể (sử dụng trang web, ngoại lệ). Nó không mở rộng tốt cho nhiều trường hợp ngoại lệ hoặc sử dụng không phổ biến.
Brian Goetz

2
@hoodaticus Tôi đồng ý với bạn. Nói rằng, bạn thích gói nhiều hơn và nhiều hơn (như được hiển thị ở trên, làm tăng nguy cơ "quên") hoặc chỉ tạo 4 giao diện thông minh và sử dụng gói lambdas w / o, như thể hiện trong stackoverflow.com/a/30974991/2365724 ? Cảm ơn
PaoloC

10
Thành thật mà nói, giải pháp này là hoàn toàn không khả thi. Tôi nghĩ rằng điểm của các luồng là để giảm nồi hơi, không tăng nó.
wvdz

24

Bạn có thể!

Mở rộng @marcg UtilExceptionvà thêm vào throw Ekhi cần thiết: theo cách này, trình biên dịch sẽ yêu cầu bạn thêm các mệnh đề ném và mọi thứ như thể bạn có thể ném ngoại lệ được kiểm tra một cách tự nhiên vào các luồng của java 8.

Hướng dẫn: chỉ cần sao chép / dán LambdaExceptionUtilvào IDE của bạn và sau đó sử dụng nó như hiển thị bên dưới LambdaExceptionUtilTest.

public final class LambdaExceptionUtil {

    @FunctionalInterface
    public interface Consumer_WithExceptions<T, E extends Exception> {
        void accept(T t) throws E;
    }

    @FunctionalInterface
    public interface Function_WithExceptions<T, R, E extends Exception> {
        R apply(T t) throws E;
    }

    /**
     * .forEach(rethrowConsumer(name -> System.out.println(Class.forName(name))));
     */
    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) {
                throwActualException(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) {
                throwActualException(exception);
                return null;
            }
        };
    }

    @SuppressWarnings("unchecked")
    private static <E extends Exception> void throwActualException(Exception exception) throws E {
        throw (E) exception;
    }

}

Một số thử nghiệm để hiển thị cách sử dụng và hành vi:

public class LambdaExceptionUtilTest {

    @Test(expected = MyTestException.class)
    public void testConsumer() throws MyTestException {
        Stream.of((String)null).forEach(rethrowConsumer(s -> checkValue(s)));
    }

    private void checkValue(String value) throws MyTestException {
        if(value==null) {
            throw new MyTestException();
        }
    }

    private class MyTestException extends Exception { }

    @Test
    public void testConsumerRaisingExceptionInTheMiddle() {
        MyLongAccumulator accumulator = new MyLongAccumulator();
        try {
            Stream.of(2L, 3L, 4L, null, 5L).forEach(rethrowConsumer(s -> accumulator.add(s)));
            fail();
        } catch (MyTestException e) {
            assertEquals(9L, accumulator.acc);
        }
    }

    private class MyLongAccumulator {
        private long acc = 0;
        public void add(Long value) throws MyTestException {
            if(value==null) {
                throw new MyTestException();
            }
            acc += value;
        }
    }

    @Test
    public void testFunction() throws MyTestException {
        List<Integer> sizes = Stream.of("ciao", "hello").<Integer>map(rethrowFunction(s -> transform(s))).collect(toList());
        assertEquals(2, sizes.size());
        assertEquals(4, sizes.get(0).intValue());
        assertEquals(5, sizes.get(1).intValue());
    }

    private Integer transform(String value) throws MyTestException {
        if(value==null) {
            throw new MyTestException();
        }
        return value.length();
    }

    @Test(expected = MyTestException.class)
    public void testFunctionRaisingException() throws MyTestException {
        Stream.of("ciao", null, "hello").<Integer>map(rethrowFunction(s -> transform(s))).collect(toList());
    }

}

1
Xin lỗi @setheron bạn đúng, chỉ cần thêm <Integer>trước map. Trong thực tế, trình biên dịch java không thể suy ra Integerkiểu trả về. Mọi thứ khác nên được chính xác.
PaoloC

1
Điều này làm việc cho tôi. Nó làm cho câu trả lời của MarcG trở nên hoàn hảo bằng cách thực thi xử lý ngoại lệ.
Skychan

1
Giải pháp cho vấn đề trên: Khai báo biến như người tiêu dùng này <ThingType> biểu thức = rethrowConsumer ((ThingType thing) -> thing.clone ()); sau đó sử dụng biểu thức đó bên trong foreach bên trong.
Skychan

1
@Skychan: Vì trong phiên bản mới được sửa đổi này, bạn không còn loại bỏ bất kỳ ngoại lệ nào nữa, có lẽ khó khăn hơn một chút đối với hệ thống suy luận. Trong một số bình luận dưới đây, Brian Goetz nói về "tính minh bạch ngoại lệ" dẫn đến "các vấn đề suy luận không thể giải quyết được".
MarcG

3
Rất đẹp. Điều đáng tiếc duy nhất là nó không hoạt động hoàn hảo với một phương pháp đưa ra nhiều ngoại lệ được kiểm tra. Trong trường hợp này, trình biên dịch sẽ làm cho bạn bắt được một siêu kiểu phổ biến, vd Exception.
wvdz

12

Chỉ cần sử dụng bất kỳ một trong NoException (dự án của tôi), Không được kiểm tra , ném lambdas , giao diện Ném được hoặc Faux Pas .

// NoException
stream.map(Exceptions.sneak().function(Class::forName));

// jOOλ
stream.map(Unchecked.function(Class::forName));

// throwing-lambdas
stream.map(Throwing.function(Class::forName).sneakyThrow());

// Throwable interfaces
stream.map(FunctionWithThrowable.aFunctionThatUnsafelyThrowsUnchecked(Class::forName));

// Faux Pas
stream.map(FauxPas.throwingFunction(Class::forName));

7

Tôi đã viết một thư viện mở rộng API Stream để cho phép bạn đưa ra các ngoại lệ được kiểm tra. Nó sử dụng mánh khóe của Brian Goetz.

Mã của bạn sẽ trở thành

public List<Class> getClasses() throws ClassNotFoundException {     
    Stream<String> classNames = 
        Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String");

    return ThrowingStream.of(classNames, ClassNotFoundException.class)
               .map(Class::forName)
               .collect(Collectors.toList());
}

7

Câu trả lời này tương tự như 17 nhưng tránh định nghĩa ngoại lệ của trình bao bọc:

List test = new ArrayList();
        try {
            test.forEach(obj -> {

                //let say some functionality throws an exception
                try {
                    throw new IOException("test");
                }
                catch(Exception e) {
                    throw new RuntimeException(e);
                }
            });
        }
        catch (RuntimeException re) {
            if(re.getCause() instanceof IOException) {
                //do your logic for catching checked
            }
            else 
                throw re; // it might be that there is real runtime exception
        }

1
Đó là một giải pháp đơn giản và hiệu quả.
Lin W

2
Đây chính xác là điều Op không muốn: thử các khối trong lambda. Hơn nữa, nó chỉ hoạt động như mong đợi miễn là không có mã nào khác ngoài khối thử kết thúc một IOException trong RuntimeException. Để tránh điều này, một trình bao bọc tùy chỉnh-RuntimeException (được định nghĩa là lớp bên trong riêng) có thể được sử dụng.
Malte Hartwig

5

Bạn không thể.

Tuy nhiên, bạn có thể muốn xem một trong những dự án của tôi cho phép bạn dễ dàng thao tác hơn với việc "ném lambdas" như vậy.

Trong trường hợp của bạn, bạn sẽ có thể làm điều đó:

import static com.github.fge.lambdas.functions.Functions.wrap;

final ThrowingFunction<String, Class<?>> f = wrap(Class::forName);

List<Class> classes =
    Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .map(f.orThrow(MyException.class))
          .collect(Collectors.toList());

và bắt MyException.

Đó là một ví dụ. Một ví dụ khác là bạn có thể .orReturn()một số giá trị mặc định.

Lưu ý rằng đây là VẪN một công việc đang tiến triển, nhiều hơn nữa sẽ đến. Tên tốt hơn, nhiều tính năng hơn, vv


2
Nhưng sau đó, nếu bạn muốn ném ngoại lệ được kiểm tra ban đầu, bạn sẽ phải thêm thử / bắt xung quanh luồng, để mở khóa, vẫn còn khủng khiếp! Tôi thích ý tưởng rằng bạn CÓ THỂ ném một ngoại lệ không được kiểm tra nếu bạn muốn và bạn CÓ THỂ trả về giá trị mặc định cho luồng nếu bạn muốn, nhưng tôi cũng nghĩ bạn nên thêm một số .orThrowChecked()phương thức vào dự án cho phép ném ngoại lệ được kiểm tra . Xin hãy xem UtilExceptioncâu trả lời của tôi trong trang này và xem bạn có thích ý tưởng thêm khả năng thứ ba này vào dự án của bạn không.
MarcG

"Nhưng sau đó, nếu bạn muốn ném ngoại lệ được kiểm tra ban đầu, bạn sẽ phải thêm thử / bắt xung quanh luồng, để mở khóa, vẫn còn khủng khiếp!" <- có nhưng bạn không có lựa chọn. Lambdas không thể tuyên truyền các ngoại lệ được kiểm tra ra khỏi bối cảnh của họ, đó là một "quyết định" thiết kế (tôi xem đó là một lỗ hổng, cá nhân, nhưng ohwell)
fge 27/12/14

Theo ý kiến ​​của bạn, tôi không làm tốt lắm những gì nó làm, xin lỗi; sau tất cả những gì bạn vẫn ném như không được kiểm soát, vậy nó khác với những gì tôi làm như thế nào? (ngoại trừ việc tôi có một giao diện khác cho nó)
fge 27/12/14

Dù sao, bạn được chào đón để đóng góp cho dự án! Ngoài ra, bạn có nhận thấy rằng Streamthực hiện AutoCloseable?
fge

Hãy để tôi hỏi bạn điều này: Có phải MyExceptionở trên của bạn cần phải là một ngoại lệ không được kiểm soát?
MarcG

3

Tóm tắt các ý kiến ​​trên giải pháp nâng cao là sử dụng trình bao bọc đặc biệt cho các chức năng chưa được kiểm tra với trình xây dựng như API cung cấp khả năng phục hồi, suy nghĩ lại và hỗ trợ.

Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .map(Try.<String, Class<?>>safe(Class::forName)
                  .handle(System.out::println)
                  .unsafe())
          .collect(toList());

Mã dưới đây thể hiện nó cho các giao diện Người tiêu dùng, Nhà cung cấp và Chức năng. Nó có thể được mở rộng dễ dàng. Một số từ khóa công khai đã bị xóa cho ví dụ này.

Lớp thử là điểm cuối cho mã máy khách. Các phương thức an toàn có thể có tên duy nhất cho từng loại chức năng. CheckedConsumer , CheckedSupplierCheckedFunction là các tương tự được kiểm tra của các hàm lib có thể được sử dụng độc lập với Try

CheckedBuilder là giao diện để xử lý các ngoại lệ trong một số chức năng được kiểm tra. orTry cho phép thực thi một chức năng cùng loại khác nếu trước đó không thành công. xử lý cung cấp xử lý ngoại lệ bao gồm lọc loại ngoại lệ. Trình tự xử lý là quan trọng. Giảm phương pháp an toànrethrow rethrows ngoại lệ cuối cùng trong chuỗi thực hiện. Giảm các phương thức orElseorElseGet trả về giá trị thay thế như các tùy chọn nếu tất cả các chức năng không thành công. Ngoài ra còn có phương pháp triệt tiêu . CheckedWrapper là triển khai chung của CheckedBuilder.

final class Try {

    public static <T> CheckedBuilder<Supplier<T>, CheckedSupplier<T>, T> 
        safe(CheckedSupplier<T> supplier) {
        return new CheckedWrapper<>(supplier, 
                (current, next, handler, orResult) -> () -> {
            try { return current.get(); } catch (Exception ex) {
                handler.accept(ex);
                return next.isPresent() ? next.get().get() : orResult.apply(ex);
            }
        });
    }

    public static <T> Supplier<T> unsafe(CheckedSupplier<T> supplier) {
        return supplier;
    }

    public static <T> CheckedBuilder<Consumer<T>, CheckedConsumer<T>, Void> 
        safe(CheckedConsumer<T> consumer) {
        return new CheckedWrapper<>(consumer, 
                (current, next, handler, orResult) -> t -> {
            try { current.accept(t); } catch (Exception ex) {
                handler.accept(ex);
                if (next.isPresent()) {
                    next.get().accept(t);
                } else {
                    orResult.apply(ex);
                }
            }
        });
    }

    public static <T> Consumer<T> unsafe(CheckedConsumer<T> consumer) {
        return consumer;
    }

    public static <T, R> CheckedBuilder<Function<T, R>, CheckedFunction<T, R>, R> 
        safe(CheckedFunction<T, R> function) {
        return new CheckedWrapper<>(function, 
                (current, next, handler, orResult) -> t -> {
            try { return current.applyUnsafe(t); } catch (Exception ex) {
                handler.accept(ex);
                return next.isPresent() ? next.get().apply(t) : orResult.apply(ex);
            }
        });
    }

    public static <T, R> Function<T, R> unsafe(CheckedFunction<T, R> function) {
        return function;
    }

    @SuppressWarnings ("unchecked")
    static <T, E extends Throwable> T throwAsUnchecked(Throwable exception) throws E { 
        throw (E) exception; 
    }
}

@FunctionalInterface interface CheckedConsumer<T> extends Consumer<T> {
    void acceptUnsafe(T t) throws Exception;
    @Override default void accept(T t) {
        try { acceptUnsafe(t); } catch (Exception ex) {
            Try.throwAsUnchecked(ex);
        }
    }
}

@FunctionalInterface interface CheckedFunction<T, R> extends Function<T, R> {
    R applyUnsafe(T t) throws Exception;
    @Override default R apply(T t) {
        try { return applyUnsafe(t); } catch (Exception ex) {
            return Try.throwAsUnchecked(ex);
        }
    }
}

@FunctionalInterface interface CheckedSupplier<T> extends Supplier<T> {
    T getUnsafe() throws Exception;
    @Override default T get() {
        try { return getUnsafe(); } catch (Exception ex) {
            return Try.throwAsUnchecked(ex);
        }
    }
}

interface ReduceFunction<TSafe, TUnsafe, R> {
    TSafe wrap(TUnsafe current, Optional<TSafe> next, 
            Consumer<Throwable> handler, Function<Throwable, R> orResult);
}

interface CheckedBuilder<TSafe, TUnsafe, R> {
    CheckedBuilder<TSafe, TUnsafe, R> orTry(TUnsafe next);

    CheckedBuilder<TSafe, TUnsafe, R> handle(Consumer<Throwable> handler);

    <E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R> handle(
            Class<E> exceptionType, Consumer<E> handler);

    CheckedBuilder<TSafe, TUnsafe, R> handleLast(Consumer<Throwable> handler);

    <E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R> handleLast(
            Class<E> exceptionType, Consumer<? super E> handler);

    TSafe unsafe();
    TSafe rethrow(Function<Throwable, Exception> transformer);
    TSafe suppress();
    TSafe orElse(R value);
    TSafe orElseGet(Supplier<R> valueProvider);
}

final class CheckedWrapper<TSafe, TUnsafe, R> 
        implements CheckedBuilder<TSafe, TUnsafe, R> {

    private final TUnsafe function;
    private final ReduceFunction<TSafe, TUnsafe, R> reduceFunction;

    private final CheckedWrapper<TSafe, TUnsafe, R> root;
    private CheckedWrapper<TSafe, TUnsafe, R> next;

    private Consumer<Throwable> handlers = ex -> { };
    private Consumer<Throwable> lastHandlers = ex -> { };

    CheckedWrapper(TUnsafe function, 
            ReduceFunction<TSafe, TUnsafe, R> reduceFunction) {
        this.function = function;
        this.reduceFunction = reduceFunction;
        this.root = this;
    }

    private CheckedWrapper(TUnsafe function, 
            CheckedWrapper<TSafe, TUnsafe, R> prev) {
        this.function = function;
        this.reduceFunction = prev.reduceFunction;
        this.root = prev.root;
        prev.next = this;
    }

    @Override public CheckedBuilder<TSafe, TUnsafe, R> orTry(TUnsafe next) {
        return new CheckedWrapper<>(next, this);
    }

    @Override public CheckedBuilder<TSafe, TUnsafe, R> handle(
            Consumer<Throwable> handler) {
        handlers = handlers.andThen(handler);
        return this;
    }

    @Override public <E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R> 
        handle(Class<E> exceptionType, Consumer<E> handler) {
        handlers = handlers.andThen(ex -> {
            if (exceptionType.isInstance(ex)) {
                handler.accept(exceptionType.cast(ex));
            }
        });
        return this;
    }

    @Override public CheckedBuilder<TSafe, TUnsafe, R> handleLast(
            Consumer<Throwable> handler) {
        lastHandlers = lastHandlers.andThen(handler);
        return this;
    }

    @Override public <E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R> 
        handleLast(Class<E> exceptionType, Consumer<? super E> handler) {
        lastHandlers = lastHandlers.andThen(ex -> {
            if (exceptionType.isInstance(ex)) {
                handler.accept(exceptionType.cast(ex));
            }
        });
        return this;
    }

    @Override public TSafe unsafe() {
        return root.reduce(ex -> Try.throwAsUnchecked(ex));
    }

    @Override
    public TSafe rethrow(Function<Throwable, Exception> transformer) {
        return root.reduce(ex -> Try.throwAsUnchecked(transformer.apply(ex)));
    }

    @Override public TSafe suppress() {
        return root.reduce(ex -> null);
    }

    @Override public TSafe orElse(R value) {
        return root.reduce(ex -> value);
    }

    @Override public TSafe orElseGet(Supplier<R> valueProvider) {
        Objects.requireNonNull(valueProvider);
        return root.reduce(ex -> valueProvider.get());
    }

    private TSafe reduce(Function<Throwable, R> orResult) {
        return reduceFunction.wrap(function, 
                Optional.ofNullable(next).map(p -> p.reduce(orResult)), 
                this::handle, orResult);
    }

    private void handle(Throwable ex) {
        for (CheckedWrapper<TSafe, TUnsafe, R> current = this; 
                current != null; 
                current = current.next) {
            current.handlers.accept(ex);
        }
        lastHandlers.accept(ex);
    }
}

3

TL; DR Chỉ cần sử dụng Lombok's@SneakyThrows .

Christian Hujer đã giải thích chi tiết tại sao việc ném các ngoại lệ được kiểm tra từ một luồng, nói đúng ra là không thể do các hạn chế của Java.

Một số câu trả lời khác đã giải thích các thủ thuật để khắc phục những hạn chế của ngôn ngữ nhưng vẫn có thể thực hiện yêu cầu ném "ngoại lệ được kiểm tra và không thêm các lần thử / bắt xấu xí vào luồng" , một số trong số chúng yêu cầu hàng chục dòng bổ sung của nồi hơi.

Tôi sẽ nhấn mạnh một tùy chọn khác để làm điều này là IMHO sạch hơn nhiều so với tất cả các tùy chọn khác: Lombok's @SneakyThrows. Nó đã được đề cập khi đi qua các câu trả lời khác nhưng bị chôn vùi dưới rất nhiều chi tiết không cần thiết.

Mã kết quả đơn giản như:

public List<Class> getClasses() throws ClassNotFoundException {
    List<Class> classes =
        Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
                .map(className -> getClass(className))
                .collect(Collectors.toList());
    return classes;
}

@SneakyThrows                                 // <= this is the only new code
private Class<?> getClass(String className) {
    return Class.forName(className);
}

Chúng tôi chỉ cần một Extract Methodcấu trúc lại (được thực hiện bởi IDE) và một dòng bổ sung cho @SneakyThrows. Chú thích sẽ chú ý đến việc thêm tất cả các mẫu soạn sẵn để đảm bảo rằng bạn có thể ném ngoại lệ đã kiểm tra của mình mà không cần bọc nó trong một RuntimeExceptionvà không cần phải khai báo rõ ràng.


4
Việc sử dụng lombok nên được khuyến khích.
Dragas

2

Bạn cũng có thể viết một phương thức trình bao bọc để bọc các ngoại lệ không được kiểm tra và thậm chí tăng cường trình bao bọc với tham số bổ sung đại diện cho một giao diện chức năng khác (có cùng kiểu trả về R ). Trong trường hợp này, bạn có thể truyền một hàm sẽ được thực thi và trả về trong trường hợp ngoại lệ. Xem ví dụ dưới đây:

private void run() {
    List<String> list = Stream.of(1, 2, 3, 4).map(wrapper(i ->
            String.valueOf(++i / 0), i -> String.valueOf(++i))).collect(Collectors.toList());
    System.out.println(list.toString());
}

private <T, R, E extends Exception> Function<T, R> wrapper(ThrowingFunction<T, R, E> function, 
Function<T, R> onException) {
    return i -> {
        try {
            return function.apply(i);
        } catch (ArithmeticException e) {
            System.out.println("Exception: " + i);
            return onException.apply(i);
        } catch (Exception e) {
            System.out.println("Other: " + i);
            return onException.apply(i);
        }
    };
}

@FunctionalInterface
interface ThrowingFunction<T, R, E extends Exception> {
    R apply(T t) throws E;
}

2

Đây là một quan điểm hoặc giải pháp khác nhau cho vấn đề ban đầu. Ở đây tôi chỉ ra rằng chúng ta có một tùy chọn để viết mã sẽ chỉ xử lý một tập hợp con các giá trị hợp lệ với một tùy chọn để phát hiện và xử lý các trường hợp khi ném ngoại lệ.

    @Test
    public void getClasses() {

        String[] classNames = {"java.lang.Object", "java.lang.Integer", "java.lang.Foo"};
        List<Class> classes =
                Stream.of(classNames)
                        .map(className -> {
                            try {
                                return Class.forName(className);
                            } catch (ClassNotFoundException e) {
                                // log the error
                                return null;
                            }
                        })
                        .filter(c -> c != null)
                        .collect(Collectors.toList());

        if (classes.size() != classNames.length) {
            // add your error handling here if needed or process only the resulting list
            System.out.println("Did not process all class names");
        }

        classes.forEach(System.out::println);
    }

1

Tôi đồng ý với các ý kiến ​​ở trên, khi sử dụng Stream.map, bạn bị giới hạn trong việc triển khai Hàm không ném Ngoại lệ.

Tuy nhiên, bạn có thể tạo FunctionalInterface của riêng mình mà ném như dưới đây ..

@FunctionalInterface
public interface UseInstance<T, X extends Throwable> {
  void accept(T instance) throws X;
}

sau đó thực hiện nó bằng Lambdas hoặc các tham chiếu như dưới đây.

import java.io.FileWriter;
import java.io.IOException;

//lambda expressions and the execute around method (EAM) pattern to
//manage resources

public class FileWriterEAM  {
  private final FileWriter writer;

  private FileWriterEAM(final String fileName) throws IOException {
    writer = new FileWriter(fileName);
  }
  private void close() throws IOException {
    System.out.println("close called automatically...");
    writer.close();
  }
  public void writeStuff(final String message) throws IOException {
    writer.write(message);
  }
  //...

  public static void use(final String fileName, final UseInstance<FileWriterEAM, IOException> block) throws IOException {

    final FileWriterEAM writerEAM = new FileWriterEAM(fileName);    
    try {
      block.accept(writerEAM);
    } finally {
      writerEAM.close();
    }
  }

  public static void main(final String[] args) throws IOException {

    FileWriterEAM.use("eam.txt", writerEAM -> writerEAM.writeStuff("sweet"));

    FileWriterEAM.use("eam2.txt", writerEAM -> {
        writerEAM.writeStuff("how");
        writerEAM.writeStuff("sweet");      
      });

    FileWriterEAM.use("eam3.txt", FileWriterEAM::writeIt);     

  }


 void writeIt() throws IOException{
     this.writeStuff("How ");
     this.writeStuff("sweet ");
     this.writeStuff("it is");

 }

}

1

Cách tích hợp duy nhất để xử lý các ngoại lệ được kiểm tra có thể được ném bởi một mapthao tác là gói gọn chúng trong a CompletableFuture. (An Optionallà một cách thay thế đơn giản hơn nếu bạn không cần giữ ngoại lệ.) Các lớp này nhằm cho phép bạn thể hiện các hoạt động dự phòng theo cách có chức năng.

Một vài phương thức trợ giúp không tầm thường là bắt buộc, nhưng bạn có thể đến mã tương đối ngắn gọn, trong khi vẫn cho thấy kết quả luồng của bạn phụ thuộc vào maphoạt động đã hoàn thành thành công. Đây là những gì nó trông giống như:

    CompletableFuture<List<Class<?>>> classes =
            Stream.of("java.lang.String", "java.lang.Integer", "java.lang.Double")
                  .map(MonadUtils.applyOrDie(Class::forName))
                  .map(cfc -> cfc.thenApply(Class::getSuperclass))
                  .collect(MonadUtils.cfCollector(ArrayList::new,
                                                  List::add,
                                                  (List<Class<?>> l1, List<Class<?>> l2) -> { l1.addAll(l2); return l1; },
                                                  x -> x));
    classes.thenAccept(System.out::println)
           .exceptionally(t -> { System.out.println("unable to get class: " + t); return null; });

Điều này tạo ra đầu ra sau:

[class java.lang.Object, class java.lang.Number, class java.lang.Number]

Các applyOrDiephương pháp có một Functionmà ném một ngoại lệ, và cải nó thành một Functionmà lợi nhuận một đã hoàn thành CompletableFuture- hoặc hoàn thành bình thường với kết quả chức năng ban đầu, hoặc hoàn thành đặc biệt với ngoại lệ ném.

mapHoạt động thứ hai minh họa rằng bây giờ bạn đã có một Stream<CompletableFuture<T>>thay vì chỉ một Stream<T>. CompletableFuturechỉ quan tâm đến việc thực hiện thao tác này nếu hoạt động ngược dòng thành công. API làm cho điều này bùng nổ, nhưng tương đối không đau.

Cho đến khi bạn có được collectgiai đoạn, đó là. Đây là nơi chúng tôi yêu cầu một phương pháp trợ giúp khá quan trọng. Chúng tôi muốn "nâng đỡ" một hoạt động bộ sưu tập bình thường (trong trường hợp này, toList()) "bên trong" CompletableFuture- cfCollector()cho phép chúng ta làm điều đó bằng cách sử dụng supplier, accumulator, combiner, và finisherkhông cần phải biết bất cứ điều gì ở tất cả về CompletableFuture.

Các phương thức trợ giúp có thể được tìm thấy trên GitHub trong MonadUtilslớp của tôi , đây vẫn là một công việc đang tiến triển.


1

Có lẽ, một cách tốt hơn và nhiều chức năng hơn là bọc các ngoại lệ và truyền bá chúng hơn nữa trong luồng. Hãy nhìn vào các Cố gắng loại Vavr ví dụ.

Thí dụ:

interface CheckedFunction<I, O> {
    O apply(I i) throws Exception; }

static <I, O> Function<I, O> unchecked(CheckedFunction<I, O> f) {
    return i -> {
        try {
            return f.apply(i);
        } catch(Exception ex) {

            throw new RuntimeException(ex);
        }
    } }

fileNamesToRead.map(unchecked(file -> Files.readAllLines(file)))

HOẶC LÀ

@SuppressWarnings("unchecked")
private static <T, E extends Exception> T throwUnchecked(Exception e) throws E {
    throw (E) e;
}

static <I, O> Function<I, O> unchecked(CheckedFunction<I, O> f) {
    return arg -> {
        try {
            return f.apply(arg);
        } catch(Exception ex) {
            return throwUnchecked(ex);
        }
    };
}

Việc thực hiện lần 2 tránh gói ngoại lệ trong a RuntimeException. throwUncheckedhoạt động vì hầu như luôn luôn tất cả các ngoại lệ chung được coi là không được kiểm tra trong java.


1

Tôi sử dụng loại ngoại lệ gói này:

public class CheckedExceptionWrapper extends RuntimeException {
    ...
    public <T extends Exception> CheckedExceptionWrapper rethrow() throws T {
        throw (T) getCause();
    }
}

Nó sẽ yêu cầu xử lý các ngoại lệ này một cách tĩnh:

void method() throws IOException, ServletException {
    try { 
        list.stream().forEach(object -> {
            ...
            throw new CheckedExceptionWrapper(e);
            ...            
        });
    } catch (CheckedExceptionWrapper e){
        e.<IOException>rethrow();
        e.<ServletExcepion>rethrow();
    }
}

Hãy thử trực tuyến!

Mặc dù ngoại lệ sẽ được ném lại trong rethrow()cuộc gọi đầu tiên (ồ, khái quát về Java ...), cách này cho phép có được một định nghĩa thống kê nghiêm ngặt về các ngoại lệ có thể xảy ra (yêu cầu phải khai báo chúng throws). Và không có instanceofhoặc một cái gì đó là cần thiết.


-1

Tôi nghĩ rằng cách tiếp cận này là đúng:

public List<Class> getClasses() throws ClassNotFoundException {
    List<Class> classes;
    try {
        classes = Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String").map(className -> {
            try {
                return Class.forName(className);
            } catch (ClassNotFoundException e) {
                throw new UndeclaredThrowableException(e);
            }
        }).collect(Collectors.toList());
    } catch (UndeclaredThrowableException e) {
        if (e.getCause() instanceof ClassNotFoundException) {
            throw (ClassNotFoundException) e.getCause();
        } else {
            // this should never happen
            throw new IllegalStateException(e.getMessage(), e);
        }
    }
    return classes;
}

Bao bọc ngoại lệ được kiểm tra bên Callabletrong UndeclaredThrowableException(đó là trường hợp sử dụng cho ngoại lệ này) và mở ra bên ngoài.

Vâng, tôi thấy nó thật xấu xí, và tôi sẽ khuyên bạn không nên sử dụng lambdas trong trường hợp này và quay trở lại một vòng lặp cũ tốt, trừ khi bạn đang làm việc với một luồng song song và paralellization mang lại một lợi ích khách quan giúp chứng minh tính không thể đọc được của mã.

Như nhiều người khác đã chỉ ra, có những giải pháp cho tình huống này và tôi hy vọng một trong số họ sẽ biến nó thành phiên bản tương lai của Java.


1
(1) Đã có một số câu trả lời hiển thị một ví dụ như thế này, vậy câu trả lời của bạn thêm gì vào câu hỏi và câu hỏi chưa được đề cập? Đăng các câu trả lời trùng lặp như thế này chỉ thêm lộn xộn vào trang web. (2) OP đặc biệt nói rằng họ không muốn làm điều này. "Xin lưu ý rằng tôi KHÔNG muốn bọc ngoại lệ được kiểm tra trong một ngoại lệ thời gian chạy và thay vào đó ném ngoại lệ không được kiểm tra."
Radiodef
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.