Tại sao javac cho phép một số phôi không thể và không phải là khác?


52

Nếu tôi cố gắng chuyển a Stringthành a java.util.Date, trình biên dịch Java sẽ bắt lỗi. Vậy tại sao trình biên dịch không gắn cờ sau đây là lỗi?

List<String> strList = new ArrayList<>();                                                                      
Date d = (Date) strList;

Tất nhiên, JVM ném một ClassCastExceptionthời gian chạy, nhưng trình biên dịch không gắn cờ nó.

Hành vi tương tự với javac 1.8.0_212 và 11.0.2.


2
Không có gì đặc biệt Listở đây. Date d = (Date) new Object();
Elliott Frisch

1
Tôi đã chơi với một arduino gần đây. Tôi yêu một trình biên dịch không vui vẻ chấp nhận bất kỳ diễn viên nào và sau đó chỉ thực hiện chúng với kết quả hoàn toàn không thể đoán trước. Chuỗi thành số nguyên? Chắc chắn rồi! Nhân đôi số nguyên? Vâng thưa ngài! Chuỗi để boolean? Ít nhất thì điều đó hầu hết trở thành sai ...
Stian Yttervik

@ElliottFrisch: Có mối quan hệ thừa kế rõ ràng giữa Ngày và Đối tượng, nhưng không có mối quan hệ nào giữa Ngày và Danh sách. Vì vậy, tôi mong đợi trình biên dịch sẽ gắn cờ diễn viên này, giống như cách nó sẽ gắn cờ một chuỗi từ Chuỗi thành Ngày. Nhưng như Zabuza giải thích trong câu trả lời xuất sắc của họ, List là một giao diện, vì vậy dàn diễn viên sẽ hợp pháp nếu strListlà một thể hiện của một lớp thực hiện Danh sách.
Mike Woinoski

Đây là một câu hỏi thường xuyên lặp lại và tôi chắc chắn rằng tôi đã thấy nhiều bản sao của nó. Về cơ bản, đây là phiên bản đảo ngược của liên quan mạnh mẽ: stackoverflow.com/questions/21812289/ trên
Hulk

1
@StianYttervik -fpermissive là những gì đang làm điều đó. Bật cảnh báo trình biên dịch.
bobsburner

Câu trả lời:


86

Các diễn viên kỹ thuật có thể. Nó không thể dễ dàng được chứng minh bởi javac rằng nó không phải như vậy trong trường hợp của bạn và JLS thực sự định nghĩa đây là một chương trình Java hợp lệ, do đó việc gắn cờ lỗi sẽ không chính xác.

Điều này là do Listmột giao diện. Vì vậy, bạn có thể có một lớp con của một Datethực tế thực hiện Listngụy trang như Listở đây - và sau đó đúc nó Datesẽ hoàn toàn ổn. Ví dụ:

public class SneakyListDate extends Date implements List<Foo> {
    ...
}

Và sau đó:

List<Foo> list = new SneakyListDate();
Date date = (Date) list; // This one is valid, compiles and runs just fine

Việc phát hiện một kịch bản như vậy có thể không phải lúc nào cũng có thể, vì nó sẽ yêu cầu thông tin thời gian chạy nếu ví dụ đến từ, ví dụ, một phương thức thay thế. Và thậm chí nếu, nó sẽ đòi hỏi nhiều nỗ lực hơn cho trình biên dịch. Trình biên dịch chỉ ngăn chặn các phôi hoàn toàn không thể do không có cách nào để cây lớp khớp hoàn toàn. Đó không phải là trường hợp ở đây, như đã thấy.

Lưu ý rằng JLS yêu cầu mã của bạn phải là một chương trình Java hợp lệ. Trong 5.1.6.1. Chuyển đổi tham chiếu được phép thu hẹp cho biết:

Chuyển đổi tham chiếu thu hẹp tồn tại từ loại Stham chiếu sang loại tham chiếu Tnếu tất cả các điều sau đây là đúng :

  • [...]
  • Một trong những trường hợp sau đây được áp dụng :
    • [...]
    • Slà một loại giao diện, Tlà một loại lớp và Tkhông đặt tên một finallớp.

Vì vậy, ngay cả khi trình biên dịch có thể nhận ra rằng trường hợp của bạn thực sự không thể chứng minh được, thì nó cũng không được phép gắn cờ vì JLS định nghĩa nó là chương trình Java hợp lệ.

Nó sẽ chỉ được phép hiển thị một cảnh báo.


16
Và đáng chú ý, lý do mà nó bắt được trường hợp với String, là String là cuối cùng, vì vậy trình biên dịch biết rằng không có lớp nào có thể mở rộng nó.
MTilsted

5
Trên thực tế, tôi không nghĩ rằng đó là "sự cuối cùng" của String khiến cho myDate = (Date) myStringthất bại. Sử dụng thuật ngữ JLS, câu lệnh cố gắng chuyển đổi từ S(the String) sang T(the Date). Ở đây, Skhông phải là một loại giao diện, vì vậy điều kiện JLS được trích dẫn ở trên không áp dụng. Ví dụ: hãy thử chuyển Lịch thành Ngày và bạn sẽ gặp lỗi trình biên dịch mặc dù không có lớp nào là cuối cùng.
Mike Woinoski

1
Tôi không biết liệu có thất vọng hay không trình biên dịch không thể thực hiện đủ phân tích tĩnh để chứng minh rằng strList chỉ có thể là kiểu ArrayList.
Joshua

3
Trình biên dịch không bị cấm kiểm tra. Nhưng nó bị cấm gọi nó là một lỗi. Điều đó sẽ làm cho trình biên dịch không tuân thủ. (Xem câu trả lời của tôi ...)
Stephen C

3
Để thêm một chút biệt ngữ, trình biên dịch sẽ cần phải chứng minh rằng loại Date & Listnày không thể ở được , không đủ để chứng minh rằng nó hiện không có người ở (có thể trong tương lai).
Polygnome

15

Hãy để chúng tôi xem xét một khái quát về ví dụ của bạn:

List<String> strList = someMethod();       
Date d = (Date) strList;

Đây là những lý do chính tại sao Date d = (Date) strList;không phải là một lỗi biên dịch.

  • Các lý do trực quan là trình biên dịch không (nói chung) biết loại chính xác của đối tượng được trả về bởi đó gọi phương thức. Có thể là ngoài việc là một lớp thực hiện List, nó còn là một lớp con của Date.

  • Các lý do kỹ thuật là ngôn ngữ Java Specification "cho phép" các chuyển đổi tài liệu tham khảo hẹp tương ứng với loại dàn diễn viên này. Theo JLS 5.1.6.1 :

    "Chuyển đổi tham chiếu thu hẹp tồn tại từ loại Stham chiếu sang loại tham chiếu Tnếu tất cả các điều sau đây là đúng:"

    ...

    5) " Slà loại giao diện, Tlà loại lớp và Tkhông đặt tên finallớp."

    ...

    Ở một nơi khác, JLS cũng nói rằng một ngoại lệ có thể bị ném khi chạy ...

    Lưu ý rằng việc xác định JLS 5.1.6.1 chỉ dựa trên các loại khai báo của các biến có liên quan thay vì các loại thời gian chạy thực tế. Trong trường hợp chung, trình biên dịch không và không thể biết các kiểu thời gian chạy thực tế.


Vậy, tại sao trình biên dịch Java không thể làm việc mà dàn diễn viên sẽ không hoạt động?

  • Trong ví dụ của tôi, someMethodcuộc gọi có thể trả về các đối tượng với nhiều loại khác nhau. Ngay cả khi trình biên dịch có thể phân tích phần thân phương thức và xác định tập hợp chính xác các kiểu có thể được trả về, không có gì ngăn người khác thay đổi nó để trả về các kiểu khác nhau ... sau khi biên dịch mã gọi nó. Đây là lý do cơ bản tại sao JLS 5.1.6.1 nói những gì nó nói.

  • Trong ví dụ của bạn, một trình biên dịch thông minh có thể chỉ ra rằng dàn diễn viên không bao giờ có thể thành công. Và nó được phép phát ra một cảnh báo thời gian biên dịch để chỉ ra vấn đề.

Vậy tại sao trình biên dịch thông minh không được phép nói đây là lỗi?

  • Bởi vì JLS nói rằng đây là một chương trình hợp lệ. Giai đoạn = Stage. Bất kỳ trình biên dịch nào gọi đây là lỗi sẽ không tuân thủ Java.

  • Ngoài ra, bất kỳ trình biên dịch nào từ chối các chương trình Java mà JLS và các trình biên dịch khác nói là hợp lệ, đều gây trở ngại cho tính di động của mã nguồn Java.


4
Upvote cho thực tế là sau khi biên dịch lớp gọi, việc thực hiện chức năng được gọi có thể thay đổi , vì vậy ngay cả khi có thể chứng minh được vào thời gian biên dịch, với việc triển khai hiện tại của callee, thì việc truyền diễn viên là không thể, điều này có thể không xảy ra vào những lần chạy sau khi callee đã thay đổi hoặc được thay thế.
Peter - Tái lập Monica

2
Upvote để làm nổi bật vấn đề tính di động sẽ được giới thiệu nếu trình biên dịch cố gắng quá thông minh.
Mike Woinoski

2

5.5.1. Kiểu tham chiếu:

Với một loại thời gian biên dịch tài liệu tham khảo S(nguồn) và một loại tài liệu tham khảo thời gian biên dịch T(mục tiêu), một chuyển đổi đúc tồn tại từ Sđể Tnếu không có lỗi thời gian biên dịch xảy ra do các quy tắc sau.

[...]

Nếu Slà một loại giao diện:

  • [...]

  • Nếu Tlà một lớp hoặc giao diện kiểu đó không phải là cuối cùng, sau đó nếu tồn tại một siêu kiểu Xcủa T, và một supertype Ycủa S, sao cho cả hai XYnhiều loại tham số có thể chứng minh rõ rệt, và rằng tẩy xóa của XYđều giống nhau, một lỗi thời gian biên dịch xảy ra

    Mặt khác, dàn diễn viên luôn hợp pháp tại thời điểm biên dịch (bởi vì ngay cả khi Tkhông thực hiện S, một lớp con Tcó thể).

List<String>SDateTtrong trường hợp của bạn.

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.