Một tính năng đặc biệt của suy luận kiểu ngoại lệ trong Java 8


85

Trong khi viết mã cho một câu trả lời khác trên trang web này, tôi đã bắt gặp điều đặc biệt này:

static void testSneaky() {
  final Exception e = new Exception();
  sneakyThrow(e);    //no problems here
  nonSneakyThrow(e); //ERRROR: Unhandled exception: java.lang.Exception
}

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

static <T extends Throwable> void nonSneakyThrow(T t) throws T {
  throw t;
}

Đầu tiên, tôi khá bối rối tại sao sneakyThrowcuộc gọi lại OK với trình biên dịch. Nó đã suy ra kiểu khả thi Tnào khi không có đề cập ở bất kỳ đâu của kiểu ngoại lệ không được kiểm tra?

Thứ hai, chấp nhận rằng điều này hoạt động, tại sao sau đó trình biên dịch lại phàn nàn về nonSneakyThrowcuộc gọi? Họ có vẻ rất giống nhau.

Câu trả lời:


66

T của sneakyThrowđược suy ra là RuntimeException. Điều này có thể được theo sau từ đặc tả langauge về kiểu suy luận ( http://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html )

Đầu tiên, có một lưu ý trong phần 18.1.3:

Một giới hạn của biểu mẫu throws αhoàn toàn là thông tin: nó hướng độ phân giải để tối ưu hóa việc khởi tạo α để, nếu có thể, nó không phải là một loại ngoại lệ được kiểm tra.

Điều này không ảnh hưởng đến bất cứ điều gì, nhưng nó chỉ cho chúng ta đến phần Giải pháp (18.4), phần này có thêm thông tin về các loại ngoại lệ được suy luận với một trường hợp đặc biệt:

... Ngược lại, nếu các thiết lập ràng buộc chứa throws αi, và các cận trên đúng αi là, ít nhất, Exception, Throwable, và Object, sau đó Ti = RuntimeException.

Trường hợp này áp dụng cho sneakyThrow- giới hạn trên duy nhất là Throwable, vì vậy Tđược suy ra RuntimeExceptionlà theo thông số kỹ thuật, vì vậy nó biên dịch. Phần thân của phương thức là phi quan trọng - quá trình ép kiểu không được kiểm tra sẽ thành công trong thời gian chạy vì nó không thực sự xảy ra, để lại một phương thức có thể đánh bại hệ thống ngoại lệ đã kiểm tra thời gian biên dịch.

nonSneakyThrowkhông biên dịch vì phương thức đó Tcó giới hạn thấp hơn Exception(tức là Tphải là siêu kiểu của Exceptionhoặc Exceptionchính nó), là một ngoại lệ đã được kiểm tra, do kiểu mà nó được gọi, vì vậy Tđược suy ra là Exception.


1
@Maksym Bạn phải có nghĩa là sneakyThrowcuộc gọi. Các quy định đặc biệt về suy luận của throws Tcác hình thức không tồn tại trong các đặc điểm kỹ thuật của Java 7.
Marko Topolnik

1
Small nitpick: Trong nonSneakyThrow, Tphải là Exception, không phải là "siêu kiểu của" Exception, bởi vì nó chính xác là kiểu đối số được khai báo tại thời điểm biên dịch tại trang web gọi.
llogiq

1
@llogiq Nếu tôi đã đọc đúng thông số kỹ thuật, nó có giới hạn dưới Exceptionvà giới hạn trên của Throwable, vì vậy giới hạn trên ít nhất, là kiểu được suy ra kết quả, là Exception.
thecoop

1
@llogiq Lưu ý rằng kiểu đối số chỉ đặt giới hạn kiểu thấp hơn vì bất kỳ siêu kiểu nào của đối số cũng được chấp nhận.
Marko Topolnik

2
“hoặc Exceptionbản thân” cụm từ có thể hữu ích cho người đọc nhưng nói chung, cần lưu ý rằng các đặc điểm kỹ thuật luôn luôn sử dụng các thuật ngữ “kiểu phụ” và “siêu kiểu” theo nghĩa “bao gồm cả bản thân” ...
Holger

17

Nếu suy luận kiểu tạo ra một giới hạn trên duy nhất cho một biến kiểu, thường thì giới hạn trên được chọn làm giải pháp. Ví dụ, nếu T<<Number, giải pháp là T=Number. Mặc dù Integer, Floatv.v. cũng có thể thỏa mãn hạn chế, nhưng không có lý do chính đáng để chọn chúng Number.

Đó cũng là trường hợp của throws Tjava 5-7 : T<<Throwable => T=Throwable. (Tất cả các giải pháp ném lén đều có <RuntimeException>đối số kiểu rõ ràng , nếu không thì <Throwable>được suy ra.)

Trong java8, với sự ra đời của lambda, điều này trở nên có vấn đề. Hãy xem xét trường hợp này

interface Action<T extends Throwable>
{
    void doIt() throws T;
}

<T extends Throwable> void invoke(Action<T> action) throws T
{
    action.doIt(); // throws T
}    

Nếu chúng ta gọi với một lambda trống, điều gì sẽ Tđược suy ra là?

    invoke( ()->{} ); 

Ràng buộc duy nhất trên Tlà một giới hạn trên Throwable. Trong giai đoạn trước của java8, T=Throwablesẽ được suy ra. Xem báo cáo này tôi đã nộp.

Nhưng điều đó khá ngớ ngẩn, để suy ra Throwable, một ngoại lệ đã được kiểm tra, từ một khối trống. Một giải pháp đã được đề xuất trong báo cáo (dường như đã được JLS thông qua) -

If E has not been inferred from previous steps, and E is in the throw clause, 
and E has an upper constraint E<<X,
    if X:>RuntimeException, infer E=RuntimeException
    otherwise, infer E=X. (X is an Error or a checked exception)

tức là nếu giới hạn trên là Exceptionhoặc Throwable, hãy chọn RuntimeExceptionlàm giải pháp. Trong trường hợp này, có một lý do chính đáng để chọn loại đặc biệt của giới hạn trên.


Ý nghĩa của X:>RuntimeExceptionđoạn mã mẫu cuối cùng của bạn là gì?
marsouf

1

Với sneakyThrow, kiểu Tlà một biến kiểu chung bị giới hạn không có kiểu cụ thể (vì không có kiểu có thể đến từ đâu).

Với nonSneakyThrow, loại Tlà loại giống như lập luận, do đó trong ví dụ của bạn, Tcủa nonSneakyThrow(e);được Exception. Khi testSneaky()không khai báo một ném Exception, một lỗi được hiển thị.

Lưu ý rằng đây là một sự can thiệp đã biết của Generics với các ngoại lệ đã được kiểm tra.


Vì vậy, vì sneakyThrownó không thực sự được suy ra cho bất kỳ loại cụ thể nào, và "cast" là một loại không xác định? Tôi tự hỏi điều gì thực sự xảy ra với điều này.
Marko Topolnik
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.