Java 8: Làm thế nào để tôi làm việc với các phương thức ném ngoại lệ trong luồng?


172

Giả sử tôi có một lớp và một phương thức

class A {
  void foo() throws Exception() {
    ...
  }
}

Bây giờ tôi muốn gọi foo cho từng phiên bản Ađược phân phối bởi một luồng như:

void bar() throws Exception {
  Stream<A> as = ...
  as.forEach(a -> a.foo());
}

Câu hỏi: Làm thế nào để tôi xử lý ngoại lệ đúng cách? Mã không biên dịch trên máy của tôi vì tôi không xử lý các trường hợp ngoại lệ có thể bị ném bởi foo (). Các throws Exceptionsố barcó vẻ là vô dụng ở đây. Tại sao vậy?



Câu trả lời:


141

Bạn cần bọc cuộc gọi phương thức của mình vào một cuộc gọi khác, nơi bạn không ném ngoại lệ được kiểm tra . Bạn vẫn có thể ném bất cứ thứ gì là một lớp con của RuntimeException.

Một thành ngữ gói thông thường là một cái gì đó như:

private void safeFoo(final A a) {
    try {
        a.foo();
    } catch (Exception ex) {
        throw new RuntimeException(ex);
    }
}

(Siêu kiểu ngoại lệ Exceptionchỉ sử dụng làm ví dụ, không bao giờ cố gắng bắt nó cho mình)

Sau đó, bạn có thể gọi nó với : as.forEach(this::safeFoo).


1
Nếu bạn muốn nó là một phương thức bao bọc, tôi sẽ khai báo nó tĩnh. Nó không sử dụng bất cứ thứ gì từ 'cái này'.
aalku

206
Thật đáng buồn khi chúng ta phải làm điều này thay vì sử dụng các ngoại lệ bản địa của mình .... oh Java, nó cho đi và sau đó lấy đi
Erich

1
@Stephan Câu trả lời đó đã bị xóa, nhưng nó vẫn có sẵn ở đây: stackoverflow.com/a/27661562/309308
Michael Mrozek

3
Điều đó sẽ ngay lập tức thất bại trong việc xem xét mã trong công ty của tôi: chúng tôi không được phép đưa ra các ngoại lệ không được kiểm soát.
Stelios Adamantidis

7
@SteliosAdamantidis quy tắc đó cực kỳ hạn chế và phản tác dụng. Bạn có biết rằng tất cả các phương thức, trong tất cả các api đều được ủy quyền để ném tất cả các ngoại lệ của thời gian chạy mà không cần bạn phải biết (tất nhiên là bạn)? Bạn đã cấm javascript vì không có khái niệm ngoại lệ được kiểm tra? Nếu tôi là nhà phát triển chính của bạn, tôi sẽ cấm các trường hợp ngoại lệ được kiểm tra thay thế.
spi

35

Nếu tất cả những gì bạn muốn là gọi foovà bạn muốn truyền bá ngoại lệ như là (không có gói), bạn cũng có thể chỉ sử dụng forvòng lặp của Java (sau khi biến Stream thành Iterable với một số mánh khóe ):

for (A a : (Iterable<A>) as::iterator) {
   a.foo();
}

Ít nhất, đây là những gì tôi làm trong các bài kiểm tra JUnit của mình, nơi tôi không muốn gặp rắc rối trong việc bọc các ngoại lệ đã kiểm tra của mình (và trên thực tế, tôi thích các bài kiểm tra của mình hơn để ném các bài kiểm tra gốc chưa được kiểm tra)


16

Câu hỏi này có thể hơi cũ, nhưng vì tôi nghĩ câu trả lời "đúng" ở đây chỉ là một cách có thể dẫn đến một số vấn đề ẩn Các vấn đề sau trong mã của bạn. Ngay cả khi có một chút tranh cãi , các trường hợp ngoại lệ được kiểm tra vẫn tồn tại vì một lý do.

Theo tôi, cách thanh lịch nhất mà tôi có thể tìm thấy được Misha đưa ra ở đây Các ngoại lệ thời gian chạy tổng hợp trong các luồng Java 8 chỉ bằng cách thực hiện các hành động trong "tương lai". Vì vậy, bạn có thể chạy tất cả các phần làm việc và thu thập các ngoại lệ không hoạt động như một phần duy nhất. Nếu không, bạn có thể thu thập tất cả chúng trong Danh sách và xử lý chúng sau.

Một cách tiếp cận tương tự đến từ Benji Weber . Ông đề nghị tạo ra một loại riêng để thu thập các bộ phận làm việc và không làm việc.

Tùy thuộc vào những gì bạn thực sự muốn đạt được ánh xạ đơn giản giữa các giá trị đầu vào và Giá trị đầu ra xảy ra Ngoại lệ cũng có thể phù hợp với bạn.

Nếu bạn không thích bất kỳ cách nào trong số những cách này, hãy cân nhắc sử dụng (tùy thuộc vào Ngoại lệ gốc) ít nhất là một ngoại lệ riêng.


3
Điều này nên được đánh dấu là một câu trả lời chính xác. +. Nhưng tôi không thể nói đó là một bài viết tốt. Bạn nên xây dựng nó rất nhiều. Làm thế nào ngoại lệ có thể giúp đỡ? Các ngoại lệ xảy ra là gì? Tại sao bạn không mang các biến thể khác nhau ở đây? Có tất cả mã ví dụ trong tài liệu tham khảo được coi là một kiểu bài thậm chí bị cấm ở đây trên SO. Văn bản của bạn trông giống như một loạt các ý kiến.
Gangnus

2
Bạn sẽ có thể chọn ở cấp độ nào bạn muốn bắt chúng và phát trực tiếp với điều đó. API Stream sẽ cho phép bạn mang ngoại lệ cho đến khi thao tác cuối cùng (như thu thập) và được xử lý ở đó bằng một trình xử lý hoặc được ném theo cách khác. stream.map(Streams.passException(x->mightThrowException(x))).catch(e->whatToDo(e)).collect(...). Nó sholud mong đợi các ngoại lệ và cho phép bạn xử lý chúng như tương lai.
aalku

10

Tôi đề nghị sử dụng lớp Google Guava throwables

tuyên truyền (có thể ném được)

Tuyên truyền có thể ném như hiện tại nếu đó là một phiên bản của RuntimeException hoặc Error, hoặc nếu không là phương án cuối cùng, kết thúc nó trong RuntimeException và sau đó truyền bá. **

void bar() {
    Stream<A> as = ...
    as.forEach(a -> {
        try {
            a.foo()
        } catch(Exception e) {
            throw Throwables.propagate(e);
        }
    });
}

CẬP NHẬT:

Bây giờ nó không được sử dụng:

void bar() {
    Stream<A> as = ...
    as.forEach(a -> {
        try {
            a.foo()
        } catch(Exception e) {
            Throwables.throwIfUnchecked(e);
            throw new RuntimeException(e);
        }
    });
}

4
Phương pháp này không được chấp nhận (không may).
Robert Važan

9

Bạn có thể bọc và bỏ ngoại lệ theo cách này.

class A {
    void foo() throws Exception {
        throw new Exception();
    }
};

interface Task {
    void run() throws Exception;
}

static class TaskException extends RuntimeException {
    private static final long serialVersionUID = 1L;
    public TaskException(Exception e) {
        super(e);
    }
}

void bar() throws Exception {
      Stream<A> as = Stream.generate(()->new A());
      try {
        as.forEach(a -> wrapException(() -> a.foo())); // or a::foo instead of () -> a.foo()
    } catch (TaskException e) {
        throw (Exception)e.getCause();
    }
}

static void wrapException(Task task) {
    try {
        task.run();
    } catch (Exception e) {
        throw new TaskException(e);
    }
}

9

Bạn có thể muốn làm một trong những điều sau đây:

  • tuyên truyền ngoại lệ kiểm tra,
  • bọc nó và tuyên truyền ngoại lệ không được kiểm soát, hoặc
  • bắt ngoại lệ và ngừng tuyên truyền.

Một số thư viện cho phép bạn làm điều đó một cách dễ dàng. Ví dụ dưới đây được viết bằng thư viện NoException của tôi .

// Propagate checked exception
as.forEach(Exceptions.sneak().consumer(A::foo));

// Wrap and propagate unchecked exception
as.forEach(Exceptions.wrap().consumer(A::foo));
as.forEach(Exceptions.wrap(MyUncheckedException::new).consumer(A::foo));

// Catch the exception and stop propagation (using logging handler for example)
as.forEach(Exceptions.log().consumer(Exceptions.sneak().consumer(A::foo)));

Tốt công việc trên thư viện! Tôi đã định viết một cái gì đó tương tự.
Jasper de Vries

2

Cách dễ đọc hơn:

class A {
  void foo() throws MyException() {
    ...
  }
}

Chỉ cần giấu nó trong một RuntimeExceptionđể vượt quaforEach()

  void bar() throws MyException {
      Stream<A> as = ...
      try {
          as.forEach(a -> {
              try {
                  a.foo();
              } catch(MyException e) {
                  throw new RuntimeException(e);
              }
          });
      } catch(RuntimeException e) {
          throw (MyException) e.getCause();
      }
  }

Mặc dù tại thời điểm này tôi sẽ không chống lại ai đó nếu họ nói bỏ qua các luồng và đi theo vòng lặp for, trừ khi:

  • bạn không tạo luồng của mình bằng cách sử dụng Collection.stream(), tức là không chuyển thẳng sang vòng lặp for.
  • bạn đang cố gắng sử dụng parallelstream()
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.