2015-03-04 15 views
22

Dưới đây là một đoạn mã:Tại sao có thể lấy lại một đối tượng "không đúng kiểu" từ Danh sách tham số trong Java?

import java.util.*; 
class Test 
{ 
    public static void main(String[] args) 
    { 
     List<Integer> list = new ArrayList<>(); 
     addToList(list); 
     Integer i = list.get(0); //#1 fails at run-time 
     String s = list.get(0); //#2 fails at compile-time 
     list.get(0); //#3 works fine 
     System.out.println(list.get(0)); //#4 works fine, prints "string" 
    } 
    static void addToList(List list){ 
     list.add("string"); 
    } 
} 

Tôi hiểu tại sao là nó có thể chèn một đối tượng của lớp String trong Danh sách parametrized.

Có vẻ như tôi hiểu tại sao mã được đánh dấu bằng #1#2 không thành công.

Nhưng tại sao #3#4 hoạt động? Theo như tôi hiểu, trình biên dịch thêm các phôi thích hợp sau khi xóa, vì vậy khi tôi gọi list.get(0), phương thức này sẽ trả về một đối tượng trước đó được đúc thành Số nguyên. Vậy tại sao không có ClassCastException xảy ra ở # 3 và # 4 tại thời gian chạy?

+1

Vì không phải tất cả các danh sách 'Danh sách' của bạn đều được parametrized :) –

Trả lời

23

# 3 hoạt động vì đối tượng được trả lại bởi get(int) bị bỏ qua. Bất cứ điều gì được lưu trữ tại vị trí 0 được trả về, nhưng vì không có diễn viên, không có lỗi xảy ra.

# 4 hoạt động tốt cho cùng một lý do: đối tượng được sản xuất bởi get(0) được xử lý như java.lang.Object phân lớp trong println, bởi vì toString được gọi. Vì toString() có sẵn cho tất cả các đối tượng Java, cuộc gọi hoàn tất mà không có lỗi.

+0

Bạn có nghĩa là việc truyền tới Integer chỉ diễn ra nếu i gán kết quả của get (0) cho một số biến? – fromSPb

+2

@fromSPb Có. Không có sự ép buộc nào vào 'Integer' trừ khi bạn gán giá trị cho cái gì đó cần một dàn diễn viên. – dasblinkenlight

+10

+1. JLS không thực sự xác định rằng "đúc", mỗi se, diễn ra; thay vào đó, nó chỉ định chuyển đổi chuyển nhượng ([§5.2] (http://docs.oracle.com/javase/specs/jls/se7/html/jls-5.html#jls-5.2)) và chuyển đổi lời gọi phương thức ([ §5.3] (http://docs.oracle.com/javase/specs/jls/se7/html/jls-5.html#jls-5.3)) có thể tăng ClassCastExceptions nếu kiểu thời gian chạy của cá thể không khớp với đích kiểu. OP # 3 và # 4 của OP không kích hoạt một trong hai quy tắc đó, vì vậy không có ClassCastException. – ruakh

1

Các phôi được áp dụng cho loại trả về get, không áp dụng cho số add giới thiệu ô nhiễm. Nếu không, bạn sẽ nhận được lỗi thời gian biên dịch hoặc ngoại lệ tại thời điểm đó, vì bạn không thể truyền một số String đến số Integer.

+1

Xin lỗi, nhưng dường như bạn đã hiểu nhầm câu hỏi. – ruakh

+0

(Cảm thấy tự do để giải đáp câu trả lời của bạn, bằng cách này, và tôi sẽ rút lại phiếu giảm giá của tôi. Hoặc bạn có thể xóa nó đi, nếu bạn cảm thấy rằng các câu trả lời khác là đủ.) – ruakh

9

Đầu tiên là lý do tại sao bạn có thể thêm chuỗi vào List<Integer>. Trong phương thức

static void addToList(List list){ 

bạn sử dụng loại thô. Các kiểu thô tồn tại hoàn toàn tương thích với các phiên bản Java cũ hơn và không nên được sử dụng trong mã mới. Trong phương thức addToList trình biên dịch Java không biết rằng list chỉ nên chứa số nguyên, và do đó nó không khiếu nại khi một chuỗi được thêm vào nó.

Đối với hành vi khác nhau của hai câu lệnh. Integer i = list.get(0) không bị lỗi tại thời gian biên dịch, vì Java cho rằng list chỉ chứa Integer s. Chỉ trong thời gian chạy nó chỉ ra rằng các yếu tố đầu tiên của list không phải là một số nguyên, và do đó bạn nhận được một ClassCastException.

String s = list.get(0) không thành công tại thời gian biên dịch vì trình biên dịch Java giả định rằng list chỉ chứa số nguyên và do đó giả sử bạn cố gán một tham số nguyên cho chuỗi tham chiếu.

Chỉ list.get(0) không lưu trữ kết quả của cuộc gọi phương thức. Vì vậy, không phải lúc biên dịch hay thời gian chạy cũng không có lý do gì cho thất bại.

Cuối cùng, System.out.println(list.get(0)) việc vì System.out là một PrintStream và có một phương pháp println(Object), có thể được gọi với một cuộc tranh cãi Integer.

3

4: Quá tải System.out.println (đối tượng) đang được gọi, vì số nguyên < = _T đối tượng (đọc: Integer là một đối tượng). Lưu ý rằng danh sách.get (int) trả về một đối tượng trong thời gian chạy vì tham số kiểu bị xóa. Bây giờ đọc

http://docs.oracle.com/javase/tutorial/java/generics/erasure.html

mà nói với bạn "Insert loại phôi nếu cần thiết để bảo vệ an toàn loại.", Kể từ khi một loại dàn diễn viên là không cần thiết từ Object Object các ClassCastException không thể xảy ra.

Vì lý do tương tự không có loại truyền ở mức 3, tuy nhiên, tác dụng phụ của cuộc gọi phương thức có thể xảy ra mà List.get không có.

6

Nếu bạn xem phương thức ArrayList#get. Nó là thế này:

public E get(int index) { 
    //body 
} 

Nhưng khi chạy nó thực sự là:

public Object get(int index) { 
     //body 
} 

Vì vậy, khi bạn làm Integer i = list.get(0); Trình biên dịch chuyển nó đến:

Integer i = (Integer)list.get(0); 

Bây giờ khi chạy, list.get(0) lợi nhuận một loại Object (thực tế là String). Nó bây giờ cố gắng chuyển đổi String => Integer và nó không thành công.

Bởi vì nó chỉ là:

list.get(0) 

Trình biên dịch làm thêm typecasting để bất cứ điều gì. Vì vậy, nó chỉ là, list.get(0).

System.out.println(list.get(0)); 

list.get(0) trả về một loại Object. Vì vậy, phương thức public void println(Object x) của PrintStream được gọi.

Hãy nhớ println(Object x) được gọi và không phải là println(String x).

Các vấn đề liên quan