Tại sao Java không cho phép các lớp con chung của throwable?


146

Theo Thông báo ngôn ngữ Java , ấn bản thứ 3:

Đó là lỗi thời gian biên dịch nếu một lớp chung là lớp con trực tiếp hoặc gián tiếp Throwable.

Tôi muốn hiểu tại sao quyết định này đã được đưa ra. Có gì sai với ngoại lệ chung?

(Theo như tôi biết, thuốc generic chỉ đơn giản là đường cú pháp thời gian biên dịch và chúng sẽ được dịch sang Objectbất kỳ .classtệp nào, do đó, việc khai báo một lớp chung là như thể mọi thứ trong đó là một Object. Xin hãy sửa cho tôi nếu tôi sai .)


1
Đối số loại chung được thay thế bằng giới hạn trên, theo mặc định là Object. Nếu bạn có một cái gì đó như Danh sách <? mở rộng A>, sau đó A được sử dụng trong các tệp lớp.
Torsten Marek

Cảm ơn bạn @Torsten. Tôi đã không nghĩ về trường hợp đó trước đây.
Hosam Aly

2
Đây là một câu hỏi phỏng vấn tốt, cái này.
skaffman

@TorstenMarek: Nếu một cuộc gọi myList.get(i), rõ ràng getvẫn trả về một Object. Trình biên dịch có chèn một cast Avào để nắm bắt một số ràng buộc khi chạy không? Nếu không, OP đúng là cuối cùng nó sẽ biến thành Objects khi chạy. (Tệp lớp chắc chắn chứa siêu dữ liệu về A, nhưng đó chỉ là siêu dữ liệu AFAIK.)
Mihai Danila

Câu trả lời:


155

Như đã nói, các loại không thể xác định lại, đây là một vấn đề trong trường hợp sau:

try {
   doSomeStuff();
} catch (SomeException<Integer> e) {
   // ignore that
} catch (SomeException<String> e) {
   crashAndBurn()
}

Cả hai SomeException<Integer>SomeException<String>bị xóa cùng một loại, JVM không có cách nào để phân biệt các trường hợp ngoại lệ và do đó không có cách nào để biết catchkhối nào sẽ được thực thi.


3
nhưng "đáng tin cậy" nghĩa là gì?
aberrant80

61
Vì vậy, quy tắc không nên là "các loại chung không thể phân lớp Có thể ném được" mà thay vào đó "các mệnh đề bắt phải luôn luôn sử dụng các loại thô".
Archie

3
Họ chỉ có thể không cho phép sử dụng hai khối bắt cùng loại. Vì vậy, chỉ riêng việc sử dụng someExc <Integer> là hợp pháp, chỉ sử dụng someExc <Integer> và someExc <String> với nhau là bất hợp pháp. Điều đó sẽ làm cho không có vấn đề, hoặc nó sẽ?
Viliam Búr

3
Ồ, bây giờ tôi hiểu rồi. Giải pháp của tôi sẽ gây ra sự cố với RuntimeExceptions, không phải khai báo. Vì vậy, nếu someExc là một lớp con của RuntimeException, tôi có thể ném và bắt một cách rõ ràng một sốExExc <Integer>, nhưng có thể một số chức năng khác đang âm thầm ném một sốExExc <String> và khối bắt của tôi cho someExc <Integer> cũng sẽ vô tình bắt được điều đó.
Viliam Búr

4
@ SuperJedi224 - Không. Họ làm đúng - đưa ra các ràng buộc rằng thuốc generic phải tương thích ngược.
Stephen C

14

Đây là một ví dụ đơn giản về cách sử dụng ngoại lệ:

class IntegerExceptionTest {
  public static void main(String[] args) {
    try {
      throw new IntegerException(42);
    } catch (IntegerException e) {
      assert e.getValue() == 42;
    }
  }
}

Phần thân của câu lệnh TRy đưa ra ngoại lệ với một giá trị đã cho, được bắt bởi mệnh đề bắt.

Ngược lại, định nghĩa sau đây về một ngoại lệ mới bị cấm, bởi vì nó tạo ra một loại tham số:

class ParametricException<T> extends Exception {  // compile-time error
  private final T value;
  public ParametricException(T value) { this.value = value; }
  public T getValue() { return value; }
}

Một nỗ lực để biên dịch các báo cáo ở trên một lỗi:

% javac ParametricException.java
ParametricException.java:1: a generic class may not extend
java.lang.Throwable
class ParametricException<T> extends Exception {  // compile-time error
                                     ^
1 error

Hạn chế này là hợp lý bởi vì hầu hết mọi nỗ lực để bắt một ngoại lệ như vậy đều phải thất bại, vì loại này không thể áp dụng được. Người ta có thể mong đợi một cách sử dụng ngoại lệ điển hình là một cái gì đó như sau:

class ParametricExceptionTest {
  public static void main(String[] args) {
    try {
      throw new ParametricException<Integer>(42);
    } catch (ParametricException<Integer> e) {  // compile-time error
      assert e.getValue()==42;
    }
  }
}

Điều này là không được phép, bởi vì loại trong mệnh đề bắt không thể áp dụng lại. Tại thời điểm viết bài này, trình biên dịch Sun báo cáo một loạt các lỗi cú pháp trong trường hợp như vậy:

% javac ParametricExceptionTest.java
ParametricExceptionTest.java:5: <identifier> expected
    } catch (ParametricException<Integer> e) {
                                ^
ParametricExceptionTest.java:8: ')' expected
  }
  ^
ParametricExceptionTest.java:9: '}' expected
}
 ^
3 errors

Vì các ngoại lệ không thể là tham số, cú pháp bị hạn chế sao cho loại phải được viết dưới dạng định danh, không có tham số nào sau đây.


2
Bạn có ý nghĩa gì khi bạn nói 'đáng tin cậy'? 'đáng tin cậy' không phải là một từ.
ForYourOwnood

1
Bản thân tôi không biết từ này, nhưng một tìm kiếm nhanh trong google đã cho tôi biết điều này: java.sun.com/docs/books/jls/third_edition/html/ trộm
Hosam Aly


13

Về cơ bản là vì nó được thiết kế theo cách xấu.

Vấn đề này ngăn chặn thiết kế trừu tượng sạch, ví dụ,

public interface Repository<ID, E extends Entity<ID>> {

    E getById(ID id) throws EntityNotFoundException<E, ID>;
}

Việc một điều khoản bắt sẽ thất bại đối với thuốc generic không được thống nhất là không có lý do cho điều đó. Trình biên dịch đơn giản có thể không cho phép các loại chung chung cụ thể mở rộng các tổng quát có thể ném hoặc không cho phép trong các mệnh đề bắt.


+1. câu trả lời của tôi - stackoverflow.com/questions/30759692/ từ
ZhongYu

1
Cách duy nhất họ có thể thiết kế nó tốt hơn là hiển thị ~ 10 năm mã của khách hàng không tương thích. Đó là một quyết định kinh doanh khả thi. Thiết kế đã chính xác ... với bối cảnh .
Stephen C

1
Vậy làm thế nào bạn sẽ bắt được ngoại lệ này? Cách duy nhất có thể làm việc là bắt loại thô EntityNotFoundException. Nhưng điều đó sẽ khiến các thế hệ trở nên vô dụng.
Frans

4

Generics được kiểm tra tại thời gian biên dịch cho độ chính xác của loại. Thông tin loại chung sau đó được loại bỏ trong một quá trình gọi là loại xóa . Ví dụ, List<Integer>sẽ được chuyển đổi sang loại không chung chung List.

Do xóa kiểu , tham số loại không thể được xác định tại thời gian chạy.

Giả sử bạn được phép gia hạn Throwablenhư thế này:

public class GenericException<T> extends Throwable

Bây giờ hãy xem xét các mã sau đây:

try {
    throw new GenericException<Integer>();
}
catch(GenericException<Integer> e) {
    System.err.println("Integer");
}
catch(GenericException<String> e) {
    System.err.println("String");
}

Do xóa kiểu , bộ thực thi sẽ không biết khối bắt nào sẽ thực thi.

Do đó, đó là lỗi thời gian biên dịch nếu một lớp chung là lớp con trực tiếp hoặc gián tiếp của throwable.

Nguồn: Vấn đề với loại tẩy


Cảm ơn. Đây là câu trả lời tương tự như câu trả lời của Torsten .
Hosam Aly

Không, không phải vậy. Câu trả lời của Torsten không giúp tôi, vì nó không giải thích loại tẩy / tẩy là gì.
Niềm tự hào Nerd chúc ngủ ngon

2

Tôi hy vọng rằng đó là vì không có cách nào để đảm bảo tham số hóa. Hãy xem xét các mã sau đây:

try
{
    doSomethingThatCanThrow();
}
catch (MyException<Foo> e)
{
    // handle it
}

Như bạn lưu ý, tham số hóa chỉ là đường cú pháp. Tuy nhiên, trình biên dịch cố gắng đảm bảo rằng tham số hóa vẫn nhất quán trên tất cả các tham chiếu đến một đối tượng trong phạm vi biên dịch. Trong trường hợp ngoại lệ, trình biên dịch không có cách nào để đảm bảo rằng MyException chỉ được ném từ một phạm vi mà nó đang xử lý.


Có, nhưng tại sao nó không được gắn cờ là "không an toàn", như với các diễn viên chẳng hạn?
eljenso

Bởi vì với một diễn viên, bạn đang nói với trình biên dịch "Tôi biết rằng đường dẫn thực thi này tạo ra kết quả mong đợi." Với một ngoại lệ, bạn không thể nói (đối với tất cả các trường hợp ngoại lệ có thể) "Tôi biết nơi này được ném." Nhưng, như tôi nói ở trên, đó là một phỏng đoán; Tôi đã không ở đó.
kdgregory

"Tôi biết rằng đường dẫn thực hiện này tạo ra kết quả mong đợi." Bạn không biết, bạn hy vọng như vậy. Đó là lý do tại sao chung và truyền phát không an toàn tĩnh, nhưng chúng vẫn được phép. Tôi nêu lên câu trả lời của Torsten, vì ở đó tôi thấy vấn đề. Ở đây tôi không có.
eljenso

Nếu bạn không biết rằng một đối tượng thuộc một loại cụ thể, bạn không nên truyền nó. Toàn bộ ý tưởng của một diễn viên là bạn có nhiều kiến ​​thức hơn trình biên dịch và đang biến kiến ​​thức đó thành một phần rõ ràng của mã.
kdgregory

Có, và ở đây bạn cũng có thể có nhiều kiến ​​thức hơn trình biên dịch, vì bạn muốn thực hiện chuyển đổi không được kiểm tra từ MyException sang MyException <Foo>. Có thể bạn "biết" nó sẽ là MyException <Foo>.
eljenso
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.