Java 8: Lambda-Streams, Lọc theo phương thức với ngoại lệ


168

Tôi gặp vấn đề khi thử các biểu thức Lambda của Java 8. Thông thường, nó hoạt động tốt, nhưng bây giờ tôi có các phương thức ném IOException. Tốt nhất nếu bạn xem đoạn mã sau:

class Bank{
    ....
    public Set<String> getActiveAccountNumbers() throws IOException {
        Stream<Account> s =  accounts.values().stream();
        s = s.filter(a -> a.isActive());
        Stream<String> ss = s.map(a -> a.getNumber());
        return ss.collect(Collectors.toSet());
    }
    ....
}

interface Account{
    ....
    boolean isActive() throws IOException;
    String getNumber() throws IOException;
    ....
}

Vấn đề là, nó không biên dịch được, vì tôi phải nắm bắt các ngoại lệ có thể có của isActive- và phương thức getNumber. Nhưng ngay cả khi tôi rõ ràng sử dụng một khối thử bắt như bên dưới, nó vẫn không được biên dịch vì tôi không bắt được Ngoại lệ. Vì vậy, có lỗi trong JDK hoặc tôi không biết cách bắt những Ngoại lệ này.

class Bank{
    ....
    //Doesn't compile either
    public Set<String> getActiveAccountNumbers() throws IOException {
        try{
            Stream<Account> s =  accounts.values().stream();
            s = s.filter(a -> a.isActive());
            Stream<String> ss = s.map(a -> a.getNumber());
            return ss.collect(Collectors.toSet());
        }catch(IOException ex){
        }
    }
    ....
}

Làm thế nào tôi có thể làm cho nó hoạt động? Ai đó có thể gợi ý cho tôi giải pháp đúng?




4
Câu trả lời đơn giản và đúng: bắt ngoại lệ bên trong lambda.
Brian Goetz

Câu trả lời:


211

Bạn phải bắt ngoại lệ trước khi nó thoát khỏi lambda:

s = s.filter(a -> { try { return a.isActive(); } 
                    catch (IOException e) { throw new UncheckedIOException(e); }}});

Hãy xem xét thực tế rằng lambda không được đánh giá tại nơi bạn viết nó, nhưng tại một nơi hoàn toàn không liên quan, trong một lớp JDK. Vì vậy, đó sẽ là điểm mà ngoại lệ được kiểm tra sẽ được ném ra, và tại nơi đó nó không được khai báo.

Bạn có thể đối phó với nó bằng cách sử dụng trình bao bọc lambda của bạn để dịch các ngoại lệ được kiểm tra sang các trường hợp không được kiểm tra:

public static <T> T uncheckCall(Callable<T> callable) {
  try { return callable.call(); }
  catch (RuntimeException e) { throw e; }
  catch (Exception e) { throw new RuntimeException(e); }
}

Ví dụ của bạn sẽ được viết là

return s.filter(a -> uncheckCall(a::isActive))
        .map(Account::getNumber)
        .collect(toSet());

Trong các dự án của tôi, tôi giải quyết vấn đề này mà không cần bọc; thay vào đó tôi sử dụng một phương pháp có hiệu quả từ chối kiểm tra các trường hợp ngoại lệ. Không cần phải nói, điều này cần được xử lý cẩn thận và mọi người trong dự án phải nhận thức được rằng một ngoại lệ được kiểm tra có thể xuất hiện ở nơi nó không được khai báo. Đây là mã hệ thống ống nước:

public static <T> T uncheckCall(Callable<T> callable) {
  try { return callable.call(); }
  catch (Exception e) { return sneakyThrow(e); }
}
public static void uncheckRun(RunnableExc r) {
  try { r.run(); } catch (Exception e) { sneakyThrow(e); }
}
public interface RunnableExc { void run() throws Exception; }


@SuppressWarnings("unchecked")
private static <T extends Throwable> void sneakyThrow(Throwable t) throws T {
  throw (T) t;
}

và bạn có thể mong đợi IOExceptionbị ném vào mặt, mặc dùcollect không khai báo. Trong hầu hết, nhưng không phải tất cả các trường hợp thực tế, bạn sẽ muốn chỉ nghĩ lại ngoại lệ, và xử lý nó như là một thất bại chung. Trong tất cả các trường hợp đó, không có gì bị mất trong sự rõ ràng hoặc chính xác. Chỉ cần cẩn thận với những trường hợp khác, nơi bạn thực sự muốn phản ứng với ngoại lệ tại chỗ. Nhà phát triển sẽ không nhận ra trình biên dịch rằng có một điều IOExceptioncần bắt ở đó và trên thực tế trình biên dịch sẽ phàn nàn nếu bạn cố gắng bắt nó bởi vì chúng tôi đã lừa nó tin rằng không có ngoại lệ nào có thể bị ném.


4
Tôi đã từng thấy NettyIO thực hiện "cú ném lén lút" trước đây và tôi muốn ném chiếc ghế của mình ra ngoài cửa sổ. "Cái gì? Trường hợp đó đã kiểm tra rò rỉ ngoại lệ từ đâu?" Đây là trường hợp sử dụng hợp pháp đầu tiên cho cú ném lén lút mà tôi đã thấy. Là một lập trình viên, bạn phải cảnh giác về việc chỉ ra cú ném lén lút là có thể. Có lẽ tốt hơn là chỉ tạo một giao diện luồng / impl hỗ trợ ngoại lệ được kiểm tra?
kevinarpe

8
Không có API lành mạnh nào cung cấp các ngoại lệ được kiểm tra chưa được khai báo cho khách hàng, đó là điều chắc chắn. Tuy nhiên, trong API có thể có một sự hiểu biết, ngoại lệ được kiểm tra đó có thể bị rò rỉ. Chúng không gây hại chừng nào chúng chỉ là một dấu hiệu khác của sự thất bại chung và sắp bị bắt và xử lý bán buôn.
Marko Topolnik

5
@kevinarpe Đây là lý do chính xác tại sao ném lén là một ý tưởng tồi. Shortcircuiting trình biên dịch bị ràng buộc để gây nhầm lẫn cho các nhà bảo trì trong tương lai.
Thorbjørn Ravn Andersen

29
Chỉ vì bạn không thích các quy tắc, không có nghĩa là nên đưa luật vào tay bạn. Lời khuyên của bạn là vô trách nhiệm vì nó đặt sự thuận tiện của người viết mã lên các cân nhắc quan trọng hơn nhiều về tính minh bạch và khả năng duy trì của chương trình.
Brian Goetz

34
@brian Chỉ vì một cái gì đó là một quy tắc không có nghĩa đó là một ý tưởng tốt. Nhưng tôi ngạc nhiên khi bạn coi phần thứ hai trong câu trả lời của tôi là "lời khuyên" vì tôi nghĩ rằng tôi đã làm cho nó rõ ràng một cách rõ ràng những gì tôi đề xuất như một giải pháp, và những gì tôi đưa ra như một FYI cho người đọc quan tâm, với những lời từ chối.
Marko Topolnik

29

Bạn cũng có thể tuyên truyền nỗi đau tĩnh của mình với lambdas, vì vậy toàn bộ điều này có thể đọc được:

s.filter(a -> propagate(a::isActive))

propagateở đây nhận java.util.concurrent.Callablelàm tham số và chuyển đổi bất kỳ ngoại lệ nào bị bắt trong khi gọi vào RuntimeException. Có một phương thức chuyển đổi tương tự throwables # tuyên truyền (Có thể ném) trong ổi.

Phương pháp này dường như rất cần thiết cho chuỗi phương thức lambda, vì vậy tôi hy vọng một ngày nào đó nó sẽ được thêm vào một trong những lib phổ biến hoặc hành vi lan truyền này sẽ được mặc định.

public class PropagateExceptionsSample {
    // a simplified version of Throwables#propagate
    public static RuntimeException runtime(Throwable e) {
        if (e instanceof RuntimeException) {
            return (RuntimeException)e;
        }

        return new RuntimeException(e);
    }

    // this is a new one, n/a in public libs
    // Callable just suits as a functional interface in JDK throwing Exception 
    public static <V> V propagate(Callable<V> callable){
        try {
            return callable.call();
        } catch (Exception e) {
            throw runtime(e);
        }
    }

    public static void main(String[] args) {
        class Account{
            String name;    
            Account(String name) { this.name = name;}

            public boolean isActive() throws IOException {
                return name.startsWith("a");
            }
        }


        List<Account> accounts = new ArrayList<>(Arrays.asList(new Account("andrey"), new Account("angela"), new Account("pamela")));

        Stream<Account> s = accounts.stream();

        s
          .filter(a -> propagate(a::isActive))
          .map(a -> a.name)
          .forEach(System.out::println);
    }
}

22

Lớp trình UtilExceptiontrợ 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 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 nhập tĩnh 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 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ệ được kiểm tra, bạn PHẢI thêm nó vào mệnh đề ném của phương thức 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 việc quên nó sẽ dễ dàng hơn.

• Nếu mã gọi đã xử lý ngoại lệ được kiểm tra, trình biên dịch SILL nhắc nhở 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ệ sẽ không bao giờ được ném trong phần thân của câu lệnh thử tương ứng ).

• Trong mọi trường hợp, bạn sẽ không thể bao quanh chính luồng đó để bắt ngoại lệ đã kiểm tra bên trong phương thức 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 đưa 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 thức mà theo nghĩa đen 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 đặ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 hoan nghênh.

• Nếu bạn ghét các ngoại lệ được kiểm tra và cảm thấy không bao giờ nên thêm chúng vào ngôn ngữ Java (ngày càng nhiều người nghĩ theo cách này và tôi KHÔNG phải là một trong số họ), thì đừng thêm ngoại lệ được kiểm tra vào ném mệnh đề 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ệ không được kiểm tra.

• Nếu bạn đang triển khai một giao diện nghiêm ngặt, nơi bạn không có tùy chọn để thêm một tuyên bố ném, nhưng việc ném một ngoại lệ là hoàn toàn phù hợp, thì gói một ngoại lệ chỉ để có được đặc quyền ném nó dẫn đến một ngăn xếp ngoại lệ trong đó đóng góp không có thông tin về những gì thực sự đã đi 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 trường hợp này, bạn có thể quyết định không thêm ngoại lệ được kiểm tra vào mệnh đề ném của phương thức 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 lệ được kiểm tra vào mệnh đề ném của phương thức có chứa luồng, hãy lưu ý đến 2 hậu quả của việc ném ngoại lệ CHECKED:

1) Mã cuộc gọi sẽ không thể bắt được nó theo tên (nếu bạn thử, trình biên dịch sẽ nói: Ngoại lệ không bao giờ được đưa vào phần thân của câu lệnh thử tương ứng). Nó sẽ sủi bọt 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 ngạc nhiên nhất: sẽ không còn đủ để bắt RuntimeException để có thể đảm bảo nắm bắt 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.

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


8

Bạn có khả năng có thể cuộn Streambiến thể của riêng mình bằng cách gói lambda của bạn để ném một ngoại lệ không được kiểm tra và sau đó hủy bỏ ngoại lệ không được kiểm soát đó trên các hoạt động của thiết bị đầu cuối:

@FunctionalInterface
public interface ThrowingPredicate<T, X extends Throwable> {
    public boolean test(T t) throws X;
}

@FunctionalInterface
public interface ThrowingFunction<T, R, X extends Throwable> {
    public R apply(T t) throws X;
}

@FunctionalInterface
public interface ThrowingSupplier<R, X extends Throwable> {
    public R get() throws X;
}

public interface ThrowingStream<T, X extends Throwable> {
    public ThrowingStream<T, X> filter(
            ThrowingPredicate<? super T, ? extends X> predicate);

    public <R> ThrowingStream<T, R> map(
            ThrowingFunction<? super T, ? extends R, ? extends X> mapper);

    public <A, R> R collect(Collector<? super T, A, R> collector) throws X;

    // etc
}

class StreamAdapter<T, X extends Throwable> implements ThrowingStream<T, X> {
    private static class AdapterException extends RuntimeException {
        public AdapterException(Throwable cause) {
            super(cause);
        }
    }

    private final Stream<T> delegate;
    private final Class<X> x;

    StreamAdapter(Stream<T> delegate, Class<X> x) {
        this.delegate = delegate;
        this.x = x;
    }

    private <R> R maskException(ThrowingSupplier<R, X> method) {
        try {
            return method.get();
        } catch (Throwable t) {
            if (x.isInstance(t)) {
                throw new AdapterException(t);
            } else {
                throw t;
            }
        }
    }

    @Override
    public ThrowingStream<T, X> filter(ThrowingPredicate<T, X> predicate) {
        return new StreamAdapter<>(
                delegate.filter(t -> maskException(() -> predicate.test(t))), x);
    }

    @Override
    public <R> ThrowingStream<R, X> map(ThrowingFunction<T, R, X> mapper) {
        return new StreamAdapter<>(
                delegate.map(t -> maskException(() -> mapper.apply(t))), x);
    }

    private <R> R unmaskException(Supplier<R> method) throws X {
        try {
            return method.get();
        } catch (AdapterException e) {
            throw x.cast(e.getCause());
        }
    }

    @Override
    public <A, R> R collect(Collector<T, A, R> collector) throws X {
        return unmaskException(() -> delegate.collect(collector));
    }
}

Sau đó, bạn có thể sử dụng cách này chính xác như một Stream:

Stream<Account> s = accounts.values().stream();
ThrowingStream<Account, IOException> ts = new StreamAdapter<>(s, IOException.class);
return ts.filter(Account::isActive).map(Account::getNumber).collect(toSet());

Giải pháp này sẽ đòi hỏi khá nhiều công cụ soạn thảo, vì vậy tôi khuyên bạn nên xem thư viện tôi đã thực hiện , chính xác những gì tôi đã mô tả ở đây cho toàn bộ Streamlớp (và hơn thế nữa!).


Xin chào ... lỗi nhỏ? new StreamBridge<>(ts, IOException.class);->new StreamBridge<>(s, IOException.class);
kevinarpe

1
@kevinarpe Yep. Nó cũng nên nói StreamAdapter.
Jeffrey

5

Sử dụng phương thức #propagate (). Mẫu triển khai phi ổi từ Blog Java 8 của Sam Beran :

public class Throwables {
    public interface ExceptionWrapper<E> {
        E wrap(Exception e);
    }

    public static <T> T propagate(Callable<T> callable) throws RuntimeException {
        return propagate(callable, RuntimeException::new);
    }

    public static <T, E extends Throwable> T propagate(Callable<T> callable, ExceptionWrapper<E> wrapper) throws E {
        try {
            return callable.call();
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw wrapper.wrap(e);
        }
    }
}

Liên kết blog Java 8 đã chết.
Spycho

4

Điều này không trả lời trực tiếp câu hỏi (có nhiều câu trả lời khác làm) nhưng cố gắng tránh vấn đề ngay từ đầu:

Theo kinh nghiệm của tôi, cần phải xử lý các trường hợp ngoại lệ trong một Stream (hoặc biểu thức lambda khác) thường xuất phát từ thực tế là các ngoại lệ được tuyên bố sẽ được ném từ các phương thức mà chúng không nên được ném. Điều này thường xuất phát từ việc trộn logic kinh doanh với đầu vào và đầu ra. AccountGiao diện của bạn là một ví dụ hoàn hảo:

interface Account {
    boolean isActive() throws IOException;
    String getNumber() throws IOException;
}

Thay vì ném một IOException trên mỗi getter, hãy xem xét thiết kế này:

interface AccountReader {
    Account readAccount(…) throws IOException;
}

interface Account {
    boolean isActive();
    String getNumber();
}

Phương pháp AccountReader.readAccount(…) có thể đọc một tài khoản từ cơ sở dữ liệu hoặc tệp hoặc bất cứ điều gì và đưa ra một ngoại lệ nếu nó không thành công. Nó xây dựng một Accountđối tượng đã chứa tất cả các giá trị, sẵn sàng để sử dụng. Vì các giá trị đã được tải bởi readAccount(…), các getters sẽ không ném ngoại lệ. Do đó, bạn có thể sử dụng chúng một cách tự do trong lambdas mà không cần phải bọc, che hoặc che giấu các ngoại lệ.

Tất nhiên không phải lúc nào cũng có thể làm theo cách tôi đã mô tả, nhưng thường thì nó và nó dẫn đến mã sạch hơn hoàn toàn (IMHO):

  • Phân tách mối quan tâm tốt hơn và theo nguyên tắc trách nhiệm duy nhất
  • Bản tóm tắt ít hơn: Bạn không phải làm lộn xộn mã của mình với throws IOException mà không sử dụng nhưng để đáp ứng trình biên dịch
  • Xử lý lỗi: Bạn xử lý các lỗi xảy ra - khi đọc từ tệp hoặc cơ sở dữ liệu - thay vì ở đâu đó ở giữa logic nghiệp vụ của bạn chỉ vì bạn muốn nhận giá trị trường
  • Bạn có thể làm cho Account bất biến và kiếm lợi từ những lợi thế của chúng (ví dụ an toàn luồng)
  • Bạn không cần "thủ thuật bẩn" hoặc cách giải quyết để sử dụng Accounttrong lambdas (ví dụ: trong a Stream)

4

Nó có thể được giải quyết bằng mã đơn giản dưới đây với StreamThử trong AbacusUtil :

Stream.of(accounts).filter(a -> Try.call(a::isActive)).map(a -> Try.call(a::getNumber)).toSet();

Tiết lộ tiết lộ Tôi là nhà phát triển của AbacusUtil.


3

Mở rộng giải pháp @marcg, thông thường bạn có thể ném và bắt ngoại lệ được kiểm tra trong Luồng; có nghĩa là, trình biên dịch sẽ yêu cầu bạn bắt / ném lại như bạn đang ở ngoài luồng !!

@FunctionalInterface
public interface Predicate_WithExceptions<T, E extends Exception> {
    boolean test(T t) throws E;
}

/**
 * .filter(rethrowPredicate(t -> t.isActive()))
 */
public static <T, E extends Exception> Predicate<T> rethrowPredicate(Predicate_WithExceptions<T, E> predicate) throws E {
    return t -> {
        try {
            return predicate.test(t);
        } catch (Exception exception) {
            return throwActualException(exception);
        }
    };
}

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

Sau đó, ví dụ của bạn sẽ được viết như sau (thêm các bài kiểm tra để hiển thị rõ hơn):

@Test
public void testPredicate() throws MyTestException {
    List<String> nonEmptyStrings = Stream.of("ciao", "")
            .filter(rethrowPredicate(s -> notEmpty(s)))
            .collect(toList());
    assertEquals(1, nonEmptyStrings.size());
    assertEquals("ciao", nonEmptyStrings.get(0));
}

private class MyTestException extends Exception { }

private boolean notEmpty(String value) throws MyTestException {
    if(value==null) {
        throw new MyTestException();
    }
    return !value.isEmpty();
}

@Test
public void testPredicateRaisingException() throws MyTestException {
    try {
        Stream.of("ciao", null)
                .filter(rethrowPredicate(s -> notEmpty(s)))
                .collect(toList());
        fail();
    } catch (MyTestException e) {
        //OK
    }
}

Ví dụ này không biên dịch
Roman M

Xin chào @RomanM cảm ơn bạn đã chỉ ra điều này: tôi đã sửa loại trả về bị thiếu trong phương thức "throwActualException". Chúng tôi đang sử dụng điều này trong sản xuất vì vậy tôi hy vọng nó cũng hoạt động về phía bạn.
PaoloC

3

Để thêm đúng mã xử lý IOException (vào RuntimeException), phương thức của bạn sẽ như sau:

Stream<Account> s =  accounts.values().stream();

s = s.filter(a -> { try { return a.isActive(); } 
  catch (IOException e) { throw new RuntimeException(e); }});

Stream<String> ss = s.map(a -> { try { return a.getNumber() }
  catch (IOException e) { throw new RuntimeException(e); }});

return ss.collect(Collectors.toSet());

Vấn đề bây giờ là ý IOExceptionchí phải được nắm bắt RuntimeExceptionvà chuyển đổi thànhIOException - và điều đó sẽ thêm nhiều mã hơn vào phương thức trên.

Tại sao sử dụng Streamkhi nó có thể được thực hiện như thế này - và phương thức ném IOExceptionđể không cần thêm mã cho điều đó:

Set<String> set = new HashSet<>();
for(Account a: accounts.values()){
  if(a.isActive()){
     set.add(a.getNumber());
  } 
}
return set;

1

Giữ vấn đề này trong tâm trí tôi đã phát triển một thư viện nhỏ để xử lý các trường hợp ngoại lệ và lambdas đã kiểm tra. Bộ điều hợp tùy chỉnh cho phép bạn tích hợp với các loại chức năng hiện có:

stream().map(unchecked(URI::new)) //with a static import

https://github.com/TouK/ThrowingF rối /


1

Ví dụ của bạn có thể được viết là:

import utils.stream.Unthrow;

class Bank{
   ....
   public Set<String> getActiveAccountNumbers() {
       return accounts.values().stream()
           .filter(a -> Unthrow.wrap(() -> a.isActive()))
           .map(a -> Unthrow.wrap(() -> a.getNumber()))
           .collect(Collectors.toSet());
   }
   ....
}

Lớp Unthrow có thể được lấy tại đây https://github.com/SeregaLBN/StreamUnthrower



0

Các giao diện chức năng trong Java không khai báo bất kỳ ngoại lệ được kiểm tra hoặc không được kiểm tra nào. Chúng ta cần thay đổi chữ ký của các phương thức từ:

boolean isActive() throws IOException; 
String getNumber() throwsIOException;

Đến:

boolean isActive();
String getNumber();

Hoặc xử lý nó với khối thử bắt:

public Set<String> getActiveAccountNumbers() {
  Stream<Account> s =  accounts.values().stream();
  s = s.filter(a -> 
    try{
      a.isActive();
    }catch(IOException e){
      throw new RuntimeException(e);
    }
  );
  Stream<String> ss = s.map(a -> 
    try{
      a.getNumber();
    }catch(IOException e){
      throw new RuntimeException(e);
    }
  );
  return ss.collect(Collectors.toSet());
}

Một tùy chọn khác là viết một trình bao bọc tùy chỉnh hoặc sử dụng một thư viện như throwingFunction. Với thư viện, chúng tôi chỉ cần thêm phụ thuộc vào tệp pom.xml của chúng tôi:

<dependency>
    <groupId>pl.touk</groupId>
    <artifactId>throwing-function</artifactId>
    <version>1.3</version>
</dependency>

Và sử dụng các lớp cụ thể như throwingFunction, throwingConsumer, throwingPredicate, throwingRunnable, throwingSupplier.

Cuối cùng, mã trông như thế này:

public Set<String> getActiveAccountNumbers() {
  return accounts.values().stream()
    .filter(ThrowingPredicate.unchecked(Account::isActive))
    .map(ThrowingFunction.unchecked(Account::getNumber))
    .collect(Collectors.toSet());
}

0

Tôi không thấy bất kỳ cách xử lý ngoại lệ nào được kiểm tra trong luồng (Java -8), cách duy nhất tôi áp dụng là bắt ngoại lệ được kiểm tra trong luồng và ném lại chúng dưới dạng ngoại lệ không được kiểm tra.

        Arrays.stream(VERSIONS)
        .map(version -> TemplateStore.class
                .getClassLoader().getResourceAsStream(String.format(TEMPLATE_FILE_MASK, version)))
        .map(inputStream -> {
            try {
                return ((EdiTemplates) JAXBContext.newInstance(EdiTemplates.class).createUnmarshaller()
                        .unmarshal(inputStream)).getMessageTemplate();
            } catch (JAXBException e) {
                throw new IllegalArgumentException(ERROR, e);
            }})
        .flatMap(Collection::stream)
        .collect(Collectors.toList());

Bạn đang thực sự cố gắng trả lời câu hỏi?
Nilambar Sharma

@Nilambar - Điều tôi đang cố gắng nói ở đây là, không có cách nào để xử lý ngoại lệ được kiểm tra trong khi sử dụng luồng java ... khi kết thúc mọi thứ chúng ta cần để bắt một kiểm tra và ném thời gian chạy / bỏ chọn .. Bây giờ Có hai điều, 1. nếu bạn nghĩ không đúng với sự hiểu biết của tôi, xin hãy sửa tôi Hoặc 2. nếu bạn nghĩ, bài viết của tôi không liên quan, tôi rất vui khi xóa cùng. liên quan, Atul
atul sachan

0

Nếu bạn muốn xử lý ngoại lệ trong luồng và tiếp tục xử lý các ngoại lệ bổ sung, có một bài viết xuất sắc trong DZone của Brian Vermeer sử dụng khái niệm Either. Nó cho thấy một cách tuyệt vời để xử lý tình huống này. Điều duy nhất còn thiếu là mã mẫu. Đây là một mẫu khám phá của tôi bằng cách sử dụng các khái niệm từ bài viết đó.

@Test
public void whenValuePrinted_thenPrintValue() {

    List<Integer> intStream = Arrays.asList(0, 1, 2, 3, 4, 5, 6);
    intStream.stream().map(Either.liftWithValue(item -> doSomething(item)))
             .map(item -> item.isLeft() ? item.getLeft() : item.getRight())
             .flatMap(o -> {
                 System.out.println(o);
                 return o.isPresent() ? Stream.of(o.get()) : Stream.empty();
             })
             .forEach(System.out::println);
}

private Object doSomething(Integer item) throws Exception {

    if (item == 0) {
        throw new Exception("Zero ain't a number!");
    } else if (item == 4) {
        return Optional.empty();
    }

    return item;
}
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.