Giải pháp cho các ngoại lệ được kiểm tra Java


49

Tôi đánh giá cao rất nhiều tính năng Java 8 mới về lambdas và các giao diện phương thức mặc định. Tuy nhiên, tôi vẫn cảm thấy nhàm chán với các ngoại lệ được kiểm tra. Chẳng hạn, nếu tôi chỉ muốn liệt kê tất cả các trường hiển thị của một đối tượng, tôi chỉ muốn viết điều này:

    Arrays.asList(p.getClass().getFields()).forEach(
        f -> System.out.println(f.get(p))
    );

Tuy nhiên, vì getphương thức có thể đưa ra một ngoại lệ được kiểm tra, không đồng ý với Consumerhợp đồng giao diện, nên tôi phải bắt ngoại lệ đó và viết mã sau đây:

    Arrays.asList(p.getClass().getFields()).forEach(
            f -> {
                try {
                    System.out.println(f.get(p));
                } catch (IllegalArgumentException | IllegalAccessException ex) {
                    throw new RuntimeException(ex);
                }
            }
    );

Tuy nhiên, trong hầu hết các trường hợp, tôi chỉ muốn ngoại lệ được ném như một RuntimeException và để chương trình xử lý, hoặc không, ngoại lệ không có lỗi biên dịch.

Vì vậy, tôi muốn có ý kiến ​​của bạn về cách giải quyết gây tranh cãi của tôi đối với sự phiền toái ngoại lệ được kiểm tra. Cuối cùng, tôi đã tạo ra một giao diện phụ trợ ConsumerCheckException<T>và một chức năng tiện ích rethrow( được cập nhật theo đường dẫn của nhận xét của Doval ) như sau:

  @FunctionalInterface
  public interface ConsumerCheckException<T>{
      void accept(T elem) throws Exception;
  }

  public class Wrappers {
      public static <T> Consumer<T> rethrow(ConsumerCheckException<T> c) {
        return elem -> {
          try {
            c.accept(elem);
          } catch (Exception ex) {
            /**
             * within sneakyThrow() we cast to the parameterized type T. 
             * In this case that type is RuntimeException. 
             * At runtime, however, the generic types have been erased, so 
             * that there is no T type anymore to cast to, so the cast
             * disappears.
             */
            Wrappers.<RuntimeException>sneakyThrow(ex);
          }
        };
      }

      /**
       * Reinier Zwitserloot who, as far as I know, had the first mention of this
       * technique in 2009 on the java posse mailing list.
       * http://www.mail-archive.com/javaposse@googlegroups.com/msg05984.html
       */
      public static <T extends Throwable> T sneakyThrow(Throwable t) {
          throw (T) t;
      }
  }

Và bây giờ tôi chỉ có thể viết:

    Arrays.asList(p.getClass().getFields()).forEach(
            rethrow(f -> System.out.println(f.get(p)))
    );

Tôi không chắc chắn rằng đây là thành ngữ tốt nhất để xoay quanh các ngoại lệ được kiểm tra, nhưng như tôi đã giải thích, tôi muốn có một cách thuận tiện hơn để đạt được ví dụ đầu tiên của mình mà không phải xử lý các ngoại lệ được kiểm tra và đây là cách đơn giản hơn mà tôi tìm thấy để làm điều đó.



2
Ngoài liên kết của Robert, hãy xem Ngoại lệ được kiểm tra một cách lén lút . Nếu bạn muốn, bạn có thể sử dụng sneakyThrowbên trong rethrowđể ném ngoại lệ gốc, đã kiểm tra thay vì gói nó trong một RuntimeException. Ngoài ra, bạn có thể sử dụng @SneakyThrowschú thích từ Project Lombok cũng làm điều tương tự.
Doval

1
Cũng lưu ý rằng Consumers in forEachcó thể được thực thi theo kiểu song song khi sử dụng Streams song song . Một cú ném được nâng lên từ việc héo người tiêu dùng sau đó sẽ truyền đến chuỗi cuộc gọi, trong đó 1) sẽ không ngăn chặn những người tiêu dùng đang chạy đồng thời khác, điều này có thể hoặc không phù hợp, và 2) nếu nhiều hơn một trong số những người tiêu dùng ném thứ gì đó, chỉ một trong những vật ném sẽ được nhìn thấy bởi luồng gọi.
Joonas Pulakka


2
Lambdas rất xấu.
Tulains Córdova

Câu trả lời:


16

Ưu điểm, nhược điểm và hạn chế của kỹ thuật của bạn:

  • 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. Ví dụ:

    public void test(Object p) throws IllegalAccessException {
        Arrays.asList(p.getClass().getFields()).forEach(rethrow(f -> System.out.println(f.get(p))));
    }
  • Nếu mã cuộc 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ệ 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ệ được 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 đượ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 các lần 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 thực hiện 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 stacktrace với các ngoại lệ giả đó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ó 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 đư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.

Người giới thiệu:

LƯU Ý: Nếu bạn quyết định sử dụng kỹ thuật này, bạn có thể sao chép LambdaExceptionUtillớp trình trợ giúp từ StackOverflow: https://stackoverflow.com/questions/27644361/how-can-i-throw-checked-exceptions-from-inside-java-8 dòng chảy . Nó cung cấp cho bạn việc triển khai hoàn chỉnh (Chức năng, Người tiêu dùng, Nhà cung cấp ...), với các ví dụ.


6

Trong ví dụ này, nó có thể thực sự thất bại? Đừng nghĩ như vậy, nhưng có lẽ trường hợp của bạn là đặc biệt. Nếu nó thực sự "không thể thất bại", và nó chỉ là một thứ biên dịch gây phiền nhiễu, tôi muốn bọc ngoại lệ và đưa ra Errormột nhận xét "không thể xảy ra". Làm cho mọi thứ rất rõ ràng để bảo trì. Nếu không, họ sẽ tự hỏi "làm thế nào điều này có thể xảy ra" và "ai là người xử lý việc này?"

Điều này trong một thực tế gây tranh cãi vì vậy YMMV. Tôi có thể sẽ nhận được một số downvote.


3
+1 vì bạn sẽ ném Lỗi. Đã bao nhiêu lần tôi kết thúc sau nhiều giờ gỡ lỗi tại một khối bắt chỉ chứa một dòng chú thích "không thể xảy ra" ...
Axel

3
-1 vì bạn sẽ ném Lỗi. Lỗi là để chỉ ra rằng có điều gì đó không ổn trong JVM và các cấp trên có thể xử lý chúng theo đó. Nếu bạn chọn theo cách đó, bạn nên ném RuntimeException. Một cách giải quyết khác có thể là các xác nhận (cần bật cờ -ea) hoặc ghi nhật ký.
Duros

3
@duros: Tùy thuộc vào lý do tại sao các ngoại lệ "không thể xảy ra", thực tế là nó được ném có thể chỉ ra một cái gì đó nặng nề sai. Giả sử, ví dụ, nếu một người gọi clonemột loại kín Foođược biết là hỗ trợ nó, và nó ném CloneNotSupportedException. Làm thế nào điều đó có thể xảy ra trừ khi mã được liên kết với một số loại bất ngờ khác Foo? Và nếu điều đó xảy ra, bất cứ điều gì có thể được tin tưởng?
supercat

1
@supercat ví dụ tuyệt vời. Những người khác đang ném ngoại lệ từ Mã hóa chuỗi bị thiếu hoặc MessageDigests Nếu UTF-8 hoặc SHA bị thiếu thời gian chạy của bạn có khả năng bị hỏng.
dùng949300

1
@duros Đó là một quan niệm sai lầm hoàn toàn. An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch.(trích dẫn từ Javadoc của Error) Đây chính xác là loại tình huống đó, do đó không đủ điều kiện, ném Errorvào đây là lựa chọn thích hợp nhất.
biziclop

1

một phiên bản khác của mã này, nơi ngoại lệ được kiểm tra chỉ bị trì hoãn:

public class Cocoon {
         static <T extends Throwable> T forgetThrowsClause(Throwable t) throws T{
            throw (T) t;
        }

        public static <X, T extends Throwable> Consumer<X> consumer(PeskyConsumer<X,T> touchyConsumer) throws T {
            return new Consumer<X>() {
                @Override
                public void accept(X t) {
                    try {
                        touchyConsumer.accept(t);
                    } catch (Throwable exc) {
                        Cocoon.<RuntimeException>forgetThrowsClause(exc) ;
                    }

                }
            } ;
        }
// and so on for Function, and other codes from java.util.function
}

điều kỳ diệu là nếu bạn gọi:

 myArrayList.forEach(Cocoon.consumer(MyClass::methodThatThrowsException)) ;

sau đó mã của bạn sẽ có nghĩa vụ bắt ngoại lệ


Điều gì xảy ra nếu điều này được chạy trên một luồng song song nơi các phần tử được tiêu thụ trong các luồng khác nhau?
prunge

0

sneakyThrow thật tuyệt! Tôi hoàn toàn cảm thấy nỗi đau của bạn.

Nếu bạn phải ở lại Java ...

Paguro có các giao diện chức năng bao gồm các ngoại lệ được kiểm tra để bạn không phải suy nghĩ về điều này nữa. Nó bao gồm các bộ sưu tập bất biến và các phép biến đổi chức năng dọc theo các dòng của bộ chuyển đổi Clojure (hoặc Luồng Java 8) có các giao diện chức năng và bao bọc các ngoại lệ được kiểm tra.

Ngoài ra còn có VAVrBộ sưu tập Eclipse để dùng thử.

Nếu không, hãy sử dụng Kotlin

Kotlin tương thích 2 chiều với Java, không có ngoại lệ được kiểm tra, không có nguyên thủy (tốt, không phải lập trình viên phải suy nghĩ), một hệ thống loại tốt hơn, giả định tính bất biến, v.v. Mặc dù tôi quản lý thư viện Paguro ở trên, tôi ' m chuyển đổi tất cả các mã Java tôi có thể sang Kotlin. Bất cứ điều gì tôi từng làm trong Java bây giờ tôi thích làm hơn trong Kotlin.


Ai đó đã bỏ phiếu này, điều này tốt, nhưng tôi sẽ không học được gì trừ khi bạn cho tôi biết lý do tại sao bạn bỏ phiếu xuống.
GlenPeterson

1
+1 cho thư viện của bạn trong github, Bạn cũng có thể kiểm tra eclipse.org/collections hoặc dự án vavr cung cấp các bộ sưu tập chức năng và bất biến. Suy nghĩ của bạn về những điều này là gì?
hỏa hoạn

-1

Các ngoại lệ được kiểm tra là rất hữu ích và việc xử lý chúng phụ thuộc vào loại sản phẩm mà bạn mã là một phần của:

  • Thư viện
  • một ứng dụng máy tính để bàn
  • một máy chủ chạy trong một số hộp
  • một bài tập học thuật

Thông thường, một thư viện không được xử lý các ngoại lệ được kiểm tra, nhưng khai báo là được ném bởi API công khai. Ví dụ, trường hợp hiếm hoi khi họ có thể gói gọn vào RuntimeEx805ton, ví dụ, tạo bên trong mã thư viện một số tài liệu xml riêng và phân tích cú pháp, tạo một số tệp tạm thời và sau đó đọc nó.

Đối với các ứng dụng máy tính để bàn, bạn phải bắt đầu phát triển sản phẩm bằng cách tạo khung xử lý ngoại lệ của riêng bạn. Sử dụng khung làm việc của riêng bạn thường bạn sẽ bọc một ngoại lệ được kiểm tra vào một trong hai đến bốn hàm bao (các lớp con tùy chỉnh của RuntimeEx805ion) và xử lý chúng bằng một Trình xử lý ngoại lệ duy nhất.

Đối với máy chủ thường mã của bạn sẽ chạy bên trong một số khung. Nếu bạn không sử dụng bất kỳ (một máy chủ trống), hãy tạo khung tương tự của riêng bạn (dựa trên Runtimes) được mô tả ở trên.

Đối với các bài tập học thuật, bạn luôn có thể gói chúng trực tiếp vào RuntimeEx805ton. Ai đó cũng có thể tạo ra một khung nhỏ.


-1

Bạn có thể muốn có một cái nhìn về dự án này ; Tôi đã tạo ra nó đặc biệt vì vấn đề ngoại lệ được kiểm tra và giao diện chức năng.

Với nó, bạn có thể làm điều này thay vì viết mã tùy chỉnh của mình:

// I don't know the type of f, so it's Foo...
final ThrowingConsumer<Foo> consumer = f -> System.out.println(f.get(p));

Vì tất cả các giao diện Ném * đó đều mở rộng các đối tác Không ném của chúng, nên bạn có thể sử dụng chúng trực tiếp trong luồng:

Arrays.asList(p.getClass().getFields()).forEach(consumer);

Hoặc bạn có thể chỉ:

import static com.github.fge.lambdas.consumers.Consumers.wrap;

Arrays.asList(p.getClass().getFields())
    .forEach(wrap(f -> System.out.println(f.get(p)));

Và những thứ khác.


Vẫn còn tốt hơnfields.forEach((ThrowingConsumer<Field>) f -> out.println(f.get(o)))
dùng2418306

Xin vui lòng giải thích?
fge
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.