2009-08-14 68 views
6

Khi thực hiện một số điều không thực sự ưa thích với Java, tôi đã gặp một lỗi với generics mà tôi không thể hiểu tại sao nó không hoạt động. Mã này là:Lỗi trình biên dịch Java Generics

package test; 
import java.util.*; 
public class TestClass { 
    public static class A extends C{} 
    public static class B extends C{} 
    public static class C{} 
    public static class D<T>{} 
    public static class E<T>{} 

    public static void main(String args[]){ 
     E<D<? extends C>> a = new E<D<A>>(); 
     E<D<? extends Object>> b = new E<D<? extends C>>(); 
     E<D<? extends A>> c = new E<D<A>>(); 
     E<D<? super A>> d = new E<D<A>>(); 
     D<? extends C> e = new D<A>(); 
     D<? extends A> f = new D<A>(); 
     D<? extends A> g = new D<A>(); 
    } 
} 

Các lỗi tôi nhận được khi biên dịch là:

 
test/TestClass.java:11: incompatible types 
found : test.TestClass.E> 
required: test.TestClass.E> 
     E> a = new E>(); 
          ^
test/TestClass.java:12: incompatible types 
found : test.TestClass.E> 
required: test.TestClass.E> 
     E> b = new E>(); 
           ^
test/TestClass.java:13: incompatible types 
found : test.TestClass.E> 
required: test.TestClass.E> 
     E> c = new E>(); 
          ^
test/TestClass.java:14: incompatible types 
found : test.TestClass.E> 
required: test.TestClass.E> 
     E> d = new E>(); 
         ^
4 errors 

Nếu E<D<? extends C>> được tìm thấy, mà chắc chắn phải phù hợp với E<D<? extends Object>>, phải không? Hay tôi đã bỏ lỡ điều gì đó?

+3

đây là một câu hỏi hay cho phiên bản tiếp theo của Java Puzzlers http://www.javapuzzlers.com/ – dfa

+0

Tôi tin rằng bạn đã tình cờ gặp một trường hợp cạnh. Rất thú vị. –

+0

Điều này có thể được sử dụng nếu bạn có thể hiểu được: http://bit.ly/3RrNV3 – teabot

Trả lời

1

Kiểm tra loại đối tượng được trả về bằng lệnh Arrays.asList. Tôi sẽ đoán nó trả về một Danh sách < D <? mở rộng đối tượng C > >, đối tượng này sẽ không thể chuyển thành Danh sách < D <? kéo dài đối tượng > >.

+0

Nhìn vào dòng mới tôi vừa mới đăng. Và tại sao lại không thể đúc được? – arnebef

+0

Tất cả những gì tôi nhận xét về câu trả lời của GrzegorzOledzki. Các tài liệu tham khảo sẽ cho phép bạn sử dụng một phạm vi rộng hơn các lớp học, sau đó lớp generics gốc của bạn chấp nhận. – Zed

2

Có lẽ điều này sẽ giúp bạn hiểu:

ArrayList<Object> aList = new ArrayList<String>(); 

này không biên dịch hoặc với một lỗi tương tự.

EDIT: Tham khảo ý kiến ​​với: http://www.ibm.com/developerworks/java/library/j-jtp01255.html

+0

Điều này không biên dịch, vì sử dụng tham chiếu aList, bạn sẽ có thể chèn các đối tượng vào danh sách của bạn, nhưng nó chỉ được chứa các chuỗi. – Zed

+2

Đó là quan điểm của ông. –

1

<? extends C> quy định cụ thể giới hạn trên của các loại, có nghĩa là loại có được mở rộng từ C. <? extends Object> là rõ ràng tổng quát hơn, do đó nó không tương thích.

Hãy nghĩ về trường hợp sử dụng này. Bằng cách xác định giới hạn trên, bạn mong đợi một giao diện tối thiểu nhất định sẽ được triển khai. Giả sử bạn có phương thức doIt() được khai báo trong C. Mọi lớp mở rộng C sẽ có phương thức đó nhưng không phải mọi đối tượng mở rộng lớp (Đó là mọi lớp trong Java).

+0

Bạn đã hiểu sai, tôi cố gắng chỉ định cho arnebef

+0

Bạn có thể cần phải suy nghĩ sai cách xung quanh :) Điều này hoàn toàn trái ngược với kiểu an toàn của việc gán đối tượng. –

+0

Không làm việc, cho dù nó có ý nghĩa hay không. Các loại chung phải khớp chính xác, không chỉ bằng cách mở rộng các loại. – Jorn

0

Tệ hơn thế. Bạn thậm chí không thể làm điều này:

List<D<?>> lDQ = Arrays.asList(new D<A>()); 

Rõ ràng hơn nếu bạn thay đổi chương trình của bạn như sau:

List<D<? extends C>> a = Arrays.asList(new D<A>(), new D<B>()); //compiles 
List<D<? extends Object>> b = a; //error 

Về cơ bản, bạn đang tuyên bố b như một cái gì đó có thể chấp nhận nội dung chung (D<mọi thứ>), nhưng danh sách bạn chỉ định cho nó là nội dung chỉ chấp nhận nội dung cụ thể hơn (D<siêu lớp phổ biến gần nhất của A và B>).

Khai báo bList<D<? extends Object>> ngụ ý rằng bạn có thể, ví dụ: b.add(new D<String>()), nhưng thực tế là List<D<? extends C>>, vì vậy bạn không thể.

2

Điều này về cơ bản là trường hợp tương tự như đã đăng trước đó. Về cơ bản, trong một trường hợp generics, bạn không bao giờ được phép làm assigment này:

Hãy suy nghĩ về ví dụ này:

ArrayList<Object> alist = new ArrayList<Number>(); 

này không biên dịch vì nó không phải là loại an toàn. Bạn có thể có thể thêm Strings aList.Bạn đang cố gán một danh sách các đối tượng được bảo đảm là Số nhưng có thể là bất kỳ Số nào, với một danh sách chỉ đảm bảo bạn chứa các đối tượng nhưng có thể là bất kỳ đối tượng nào. Nếu trình biên dịch cho phép trường hợp này, nó sẽ nới lỏng hạn chế về loại đối tượng nào được phép vào danh sách. Đây là lý do tại sao bạn phải sử dụng ký tự đại diện, như vậy:

ArrayList<? extends Object> alist = new ArrayList<Number>(); 

Để trình biên dịch ArrayList<? extends Object>, có nghĩa là "một ArrayList của một số loại hình cụ thể '?' mà tôi không biết, nhưng cái mà tôi biết là mở rộng Object. ArrayList này được bảo đảm chỉ chứa đựng những yếu tố của cái chưa biết này '?' và do đó chỉ chứa các đối tượng ". Trong trường hợp này trình biên dịch sẽ không cho phép bạn làm alist.add (2). Tại sao trường hợp đó, bởi vì trình biên dịch không biết loại phần tử của danh sách và không thể đảm bảo rằng bạn được phép chèn các đối tượng Integer vào nó.

Bạn có quyền suy nghĩ rằng D<? extends Object> là siêu kiểu của D<? extends C>. Tuy nhiên, List<D<? extends Object>> không phải là loại phụ của List<D<? extends C>>, bạn nên sử dụng List<? extends D<? extends C>>.

trường hợp của bạn là về cơ bản tương đương với

ArrayList<D<? extends Object>> alist = new ArrayList<D<? extends C>>(); 

Bạn có vấn đề tương tự như trên, danh sách ở phía bên tay phải chỉ có thể chứa đối tượng của lớp D mà Parameter loại là C, và bạn đang cố gắng gán nó vào một danh sách (ở phía bên tay trái) có thể chứa các đối tượng của lớp D có tham số kiểu có thể là bất kỳ đối tượng nào.

Vì vậy, nếu trình biên dịch cho phép mã của bạn sẽ không được nhập an toàn và những điều sau sẽ không thành công.

ArrayList<D<? extends Object>> alist = new ArrayList<D<? extends C>>(); //< not type safe 
alist.add(new D<Number>); //< oops 

Nói tóm lại, những gì bạn cần cho ví dụ cụ thể của bạn như sau:

// type parameter of left hand side is ? extends subtype 
List<? extends D<? extends Object>> b = Arrays.asList(new D<A>(), new D<B>()); 

// type parameter of left hand side is identical 
List<D<? extends C>> b = Arrays.asList(new D<A>(), new D<B>()); 

// type parameter of left hand side is ? extends subtype 
List<? extends D<? extends C>> c = Arrays.asList(new D<A>()); 

// type parameter of left hand side is identical 
List<D<A>> c = Arrays.asList(new D<A>()); 

Hope this helps.

0

Điều này là List<D<? extends Object>> về cơ bản xác định một lớp mới và do đó List<D<? extends C>>. Ngay cả khi c mở rộng đối tượng không có nghĩa là List<D<? extends C>> mở rộng List<D<? extends Object>> và điều đó là cần thiết để đảm bảo hoạt động.

Althoug series of articles này được viết cho nền tảng .NET mô tả của vấn đề vẫn còn đúng cho Java Generics

+0

Bạn cần phải thoát khỏi những dấu ngoặc nhọn với backticks. –

0

Bạn có thể không kế thừa trong Generics-tham số. Giả sử bạn có các tham chiếu sau:

List<Object> a; 
List<String> b; 

Bây giờ bạn gán cả hai danh sách giống nhau: a = b;

Nếu một số mã nào sau đây:

a.add(new Integer(1)); 

gì xảy ra nếu ai đó làm như sau:

String s = b.get(0); 

Bạn sẽ nhận được một Integer của một Danh sách Strings. Điều đó không nên hoạt động. Đó là lý do tại sao List và List không tương thích, mặc dù một String có thể được gán cho một tham chiếu Object. Generics không hoạt động với thừa kế.

-1

Không.
<?mở rộng C > không giống với <? mở rộng đối tượng >
Nếu đó là trường hợp, nó sẽ dẫn cũng là những (unstated) giả định - C extends Object, và chì để nói ...

class C{ public void doSomethingMEaningful(); };

List< ? extends C > allAtSea = new ArrayList<...>(); List< ? extends Object > allObjects = new ArrayList<...>(); allObjects.add(new Integer(88)); ...

allAtSea.addAll(allObjects); ...

allAtSea.get(...).doSomethingMeaningful(...); // Uh-oh.. this finds the Integer 88


Các C++ FAQ cung cấp một ví dụ minh mẫn cho điều này tại 21.3

+0

Tôi không nghĩ rằng một ví dụ C++ giúp trong một câu hỏi Java. – Jorn

+0

Tôi cầu xin sự khác biệt, Cho dù là danh sách-của-một cái gì đó là giống như một danh sách-of-cái gì khác là về sự hiểu biết thừa kế, và thay thế.
Điều đó, IMO, là về các quỹ tài trợ OO; do đó tham chiếu đến C++ phải được chấp nhận – Everyone

+0

Ví dụ vẫn không chính xác (hoặc thậm chí là trợ giúp, IMO). Dòng allAtSea.addAll() không biên dịch và cũng không phải allObjects.add(). – Jorn

0

Tôi nghĩ điều này sẽ rõ ràng hơn nếu chúng tôi mở rộng ví dụ của bạn. Hãy đặt một số chức năng vào E và xây dựng trên dòng đầu tiên của phương pháp chính của bạn:

public static class E<T>{ 
    private final T thing; 

    public void setThing(T thing) { 
     this.thing = thing; 
    } 

    public T getThing() { 
     return thing; 
    } 
} 

public static void main(String[] args) { 
    E<D<? extends C>> a1; 
    E<D<A>> a2 = new E<D<A>>(); 
    a1 = a2; // this won't compile, but why? 

    // these things are permissible on a1: 
    a1.setThing(new D<A>()); 
    a2.setThing(new D<B>()); 

    // now let's try doing the same thing to a2: 
    a2.setThing(new D<A>()); 
    a2.setThing(new D<B>()); // oops 
} 

Dòng cuối cùng của phương pháp mới chính là lý do không thể đặt a1 thành a2. Nếu trình biên dịch cho phép bạn làm điều đó, thì bạn sẽ có thể đặt một D <B> vào một vùng chứa đã được khai báo để chỉ giữ D <A> s.

Lý do này là không rõ ràng từ ví dụ ban đầu của bạn là bởi vì bạn đã không tạo ra một biến riêng biệt mà tham chiếu đối tượng sử dụng hạn chế hơn E < D < Một > > gõ. Nhưng hy vọng bây giờ bạn có thể thấy rằng nếu một tham chiếu như vậy tồn tại, sự an toàn kiểu của nó sẽ bị tổn hại bởi nhiệm vụ bạn đã cố gắng làm.

1

Khi gán cho một biến (E<T>) với một tổ chức phi-wildcard generic loại T, đối tượng được phân công phải có chính xác T as type chung của nó (bao gồm tất cả các tham số kiểu generic của T, ký tự đại diện và không ký tự đại diện). Trong trường hợp của bạn TD<A>, không cùng loại với D<? extends C>.

gì bạn có thể làm, vì D<A> là chuyển nhượng để D<? extends C>, là sử dụng các loại wildcard:

E<? extends D<? extends C>> a = new E<D<A>(); 
+0

Tôi nghĩ câu trả lời này giải thích tốt nhất. Bạn có thể làm E > e = new E >(); nhưng không phải E > e = new E >(); – Jorn

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