2009-08-06 45 views
8
public void wahey(List<Object> list) {} 

wahey(new LinkedList<Number>()); 

Cuộc gọi đến phương thức này sẽ không đánh dấu chọn. Tôi thậm chí không thể truyền thông số như sau:Tại sao Danh sách <Number> không phải là loại phụ của Danh sách <Object>?

wahey((List<Object>) new LinkedList<Number>()); 

Từ nghiên cứu của tôi, tôi đã thu thập được lý do không cho phép loại này an toàn. Nếu chúng tôi được phép làm các việc trên, sau đó chúng ta có thể có những điều sau đây:

List<Double> ld; 
wahey(ld); 

Bên trong phương pháp wahey, chúng ta có thể thêm một số Strings vào danh sách đầu vào (như các tham số duy trì một tham chiếu List<Object>). Bây giờ, sau khi gọi phương thức, ld đề cập đến một danh sách có loại List<Double>, nhưng danh sách thực tế chứa một số đối tượng String!

Điều này có vẻ khác với cách thông thường mà Java hoạt động mà không có generics. Ví dụ:

Object o; 
Double d; 
String s; 

o = s; 
d = (Double) o; 

Điều chúng ta đang làm ở đây về cơ bản là giống nhau, ngoại trừ việc này sẽ vượt qua kiểm tra biên dịch và chỉ thất bại trong thời gian chạy. Phiên bản có Danh sách sẽ không biên dịch.

Điều này dẫn tôi tin rằng đây hoàn toàn là một quyết định thiết kế liên quan đến các hạn chế loại trên generics. Tôi đã hy vọng nhận được một số ý kiến ​​về quyết định này?

+1

liên quan (không phải là bản sao chính xác, mặc dù câu trả lời chính xác về cơ bản giống nhau): http: // stackoverflow.com/questions/251298/why-is-someclass-super-t-not-equivalent-to-someclasst-in-java-generic-types – Kip

+3

Thuật ngữ nghệ thuật là ** hiệp phương sai ** - Tôi đề cập đến điều này bởi vì nó làm cho nó dễ dàng hơn cho bạn để tìm kiếm nó, bao gồm trên Stack Overflow (nếu bạn muốn xem những gì tôi đã nói về nó - trong ngữ cảnh của C#, nhưng nó cũng áp dụng cho Java - tìm kiếm [người dùng hiệp phương sai: 95810], chẳng hạn). –

+0

Một dàn diễn viên đôi như 'wahey ((Danh sách ) (Danh sách ) new ArrayList ())' sẽ biên dịch, mặc dù điều này không được khuyến khích. – Robin479

Trả lời

9

Những gì bạn đang làm trong ví dụ "không có generics" là một dàn diễn viên, điều này làm rõ rằng bạn đang làm một điều gì đó không an toàn. Tương đương với generics sẽ là:

Object o; 
List<Double> d; 
String s; 

o = s; 
d.add((Double) o); 

Hoạt động theo cách tương tự (biên dịch, nhưng không hoạt động). Lý do không cho phép hành vi bạn đang hỏi là vì nó sẽ cho phép ẩn ngụ hành động loại không an toàn, điều này khó nhận thấy hơn trong mã. Ví dụ:

public void Foo(List<Object> list, Object obj) { 
    list.add(obj); 
} 

này trông hoàn toàn tốt đẹp và kiểu an cho đến khi bạn gọi nó là như thế này:

List<Double> list_d; 
String s; 

Foo(list_d, s); 

nào cũng trông kiểu an, bởi vì bạn là người gọi không nhất thiết phải biết những gì Foo sẽ làm gì với các thông số của nó.

Vì vậy, trong trường hợp đó, bạn có hai bit có vẻ như loại an toàn, cùng nhau kết thúc là loại không an toàn. Đó là xấu, bởi vì nó ẩn và do đó khó tránh và khó khăn hơn để gỡ lỗi.

+0

Ah, tôi hiểu rồi. Cảm ơn nhiều. :) –

8

xem xét nếu nó là ...

List<Integer> nums = new ArrayList<Integer>(); 
List<Object> objs = nums 
objs.add("Oh no!"); 
int x = nums.get(0); //throws ClassCastException 

Bạn sẽ có thể thêm bất cứ điều gì trong những loại cha mẹ vào danh sách, trong đó có thể không phải những gì nó đã được trước đây là khai báo là, mà như ví dụ trên cho thấy , gây ra tất cả các loại vấn đề. Vì vậy, nó không được phép.

+0

Điều này là đúng, nhưng câu hỏi cho thấy rằng anh ta đã hiểu điều này và muốn biết tại sao quyết định được đưa ra để cấm hành vi này trong khi vẫn cho phép các dạng hành vi không an toàn loại khác. –

+0

Tôi đã hiểu ví dụ này. Tôi đã cố gắng giải thích kịch bản này trong câu hỏi (như Tyler đã nói), nhưng cảm ơn bạn đã bình luận. –

+1

loại hành vi không an toàn này thực sự * được * cho phép với mảng. Các biên dịch sau đây chỉ tốt nhưng tạo ra một lỗi thời gian chạy: 'Object [] arr = new Integer [2]; arr [0] = "oh no!"; ' – Kip

4

Chúng không phải là phân loại của nhau do cách thức hoạt động của generics. Những gì bạn muốn là tuyên bố chức năng của bạn như thế này:

public void wahey(List<?> list) {} 

Sau đó, nó sẽ chấp nhận Danh sách bất cứ điều gì mở rộng đối tượng.Bạn cũng có thể làm:

public void wahey(List<? extends Number> list) {} 

Điều này sẽ cho phép bạn đưa vào Danh sách thứ gì đó là phân lớp của Số.

Tôi khuyên bạn nên chọn một bản sao của "Java Generics and Collections" của Maurice Naftalin & Philip Wadler.

+0

+1, đây là cách hoạt động của generics. ** DO ** nhận một bản sao của Java Generics and Collections. Cuốn sách tuyệt vời! – WolfmanDragon

+0

Đây không phải là câu hỏi của tôi. Tôi biết đây là những quy tắc đánh máy phụ với Generics. –

3

Thực chất có hai chiều trừu tượng ở đây, trừu tượng danh sách và sự trừu tượng hóa nội dung của nó. Nó hoàn toàn tốt để thay đổi theo danh sách trừu tượng - ví dụ, đó là một LinkedList hoặc một ArrayList - nhưng nó không tốt để hạn chế thêm nội dung, để nói: Điều này (danh sách chứa các đối tượng) là một (danh sách liên kết chỉ giữ số). Bởi vì bất kỳ tham chiếu nào biết nó như là một (danh sách chứa các đối tượng) hiểu, theo hợp đồng loại của nó, nó có thể giữ bất kỳ đối tượng nào.

Điều này hoàn toàn khác với những gì bạn đã làm trong mã ví dụ không phải là generics, nơi bạn đã nói: xử lý Chuỗi này như thể nó là một Đôi. Thay vào đó, bạn đang cố gắng nói: xử lý danh sách này (danh sách chỉ giữ số) dưới dạng (danh sách chứa bất kỳ thứ gì). Và nó không, và trình biên dịch có thể phát hiện nó, vì vậy nó không cho phép bạn thoát khỏi nó.

+0

Giải thích tốt, cảm ơn bạn. –

1

"What we are doing here is essentially the same thing, except this will pass compile-time checks and only fail at run-time. The version with Lists won't compile."

Điều bạn đang quan sát có ý nghĩa hoàn hảo khi bạn xem xét mục đích chính của Java Generics là loại không tương thích để thất bại trong thời gian biên dịch thay vì thời gian chạy.

Từ java.sun.com

Generics provides a way for you to communicate the type of a collection to the compiler, so that it can be checked. Once the compiler knows the element type of the collection, the compiler can check that you have used the collection consistently and can insert the correct casts on values being taken out of the collection.

0

Trong Java, List<S>không phải là một subtype của List<T> khi S là một subtype của T. Quy tắc này cung cấp loại an toàn.

Giả sử chúng tôi cho phép List<String> làm loại phụ của List<Object>. Hãy xem xét ví dụ sau:

public void foo(List<Object> objects) { 
    objects.add(new Integer(42)); 
} 

List<String> strings = new ArrayList<String>(); 
strings.add("my string"); 
foo(strings); // this is not allow in java 
// now strings has a string and an integer! 
// what would happen if we do the following...?? 
String myString = strings.get(1); 

Vì vậy, việc này cung cấp sự an toàn về loại nhưng cũng có nhược điểm, nó ít linh hoạt hơn. Hãy xem xét ví dụ sau:

class MyCollection<T> { 
    public void addAll(Collection<T> otherCollection) { 
     ... 
    } 
} 

Ở đây bạn có một bộ sưu tập gồm T, bạn muốn thêm tất cả các mục từ bộ sưu tập khác. Bạn không thể gọi phương thức này với số Collection<S> cho loại phụ số S của T. Lý tưởng nhất, điều này là ok vì bạn chỉ thêm các phần tử vào bộ sưu tập của mình, bạn không sửa đổi bộ sưu tập tham số.

Để khắc phục điều này, Java cung cấp những gì họ gọi là "ký tự đại diện". Ký tự đại diện là một cách để cung cấp hiệp phương sai/contravariance. Bây giờ xem xét việc sử dụng ký tự đại diện sau:

class MyCollection<T> { 
    // Now we allow all types S that are a subtype of T 
    public void addAll(Collection<? extends T> otherCollection) { 
     ... 

     otherCollection.add(new S()); // ERROR! not allowed (Here S is a subtype of T) 
    } 
} 

Bây giờ, với các kí hiệu chúng tôi cho phép hiệp phương sai trong các loại T và chúng tôi chặn hoạt động mà không phải là loại an toàn (ví dụ thêm một mục vào bộ sưu tập). Bằng cách này, chúng tôi có được sự linh hoạt và an toàn loại.

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