2009-02-26 29 views
10

thể trùng lặp:
what is the difference between ‘super’ and ‘extends’ in Java GenericsGenerics ..? Super T

Một)

List<? super Shape> shapeSuper = new ArrayList<Shape>(); 

shapeSuper.add(new Square());  //extends from SHAP 
shapeSuper.add(new DoubleSquare()); //extends from SQ 
shapeSuper.add(new TripleSquare()); //extends from DS 
shapeSuper.add(new Rectangle()); //extends from SHAP 
shapeSuper.add(new Circle());  //extends from SHAP 

for (Object object : shapeSuper) { ... } 

Tại sao lặp phải có đối tượng khi tôi chỉ có thể thêm Shape và dẫn xuất của nó?

B)

List<? super Shape> shapeSuper = new ArrayList<Object>(); 

shapeSuper.add(new Object()); //compilation error 

Tại sao dòng ở trên tạo ra một lỗi biên dịch?

+0

Ngạc nhiên, lợi ích của việc này là gì khi xác định giao diện Hình dạng và sau đó chỉ cần tạo Danh sách ? Bạn vẫn có thể kiểm tra các lớp học cụ thể và truyền nếu cần thiết ... – kgrad

Trả lời

4

Thay vào đó hãy thử khai báo hình dạngSuper là List<Shape>. Sau đó, bạn có thể làm

for (Shape shape : shapeSuper) 
23

Để mở rộng trên Paul's answer, bằng cách tuyên bố shapeSuper làm Danh sách <? super Shape >, bạn đang nói rằng nó có thể chấp nhận bất kỳ đối tượng nào là một lớp siêu hình dạng. Đối tượng là một siêu lớp của hình dạng. Điều này có nghĩa rằng các siêu lớp chung của mỗi phần tử của danh sách là Object.

Đây là lý do tại sao bạn phải sử dụng loại Đối tượng trong vòng lặp for. Theo như trình biên dịch có liên quan, danh sách có thể chứa các đối tượng không phải là Hình dạng.

+2

+1 để thực sự giải thích những gì sai. Thêm một cuộc thảo luận về "? Super T" so với "? Extends T" và tôi sẽ upvote một lần nữa. Oh chờ đã ... –

+0

hehe mmyers. tôi cũng thích lời giải thích., quá tệ, chúng tôi không thể upvote lần thứ hai –

3

Một)

super chỉ ra lớp viền dưới của một yếu tố chung. Vì vậy, List<? super Shape> có thể đại diện cho List<Shape> hoặc List<Object>.

B)

Bởi vì trình biên dịch không biết những gì các loại thực tế của List<? super Shape> là.

Thêm đối tượng với shapeSuper.add(new Object()); nhưng trình biên dịch chỉ biết rằng loại chung của List là loại siêu Shape nhưng không biết chính xác phù thủy.

Trong ví dụ của bạn, List<? super Shape> thực sự có thể là List<ShapeBase> buộc trình biên dịch không cho phép hoạt động shapeSuper.add(new Object());.

Hãy nhớ rằng, Generics are not covariant.

2

(Disclaimer: Tôi chưa từng sử dụng "siêu" như khuôn khổ vòng loại ký tự đại diện chung chung, do thực hiện việc này với một hạt muối ...)

Đối với (A), trên thực tế bạn không thể thêm Shape và dẫn xuất của nó, bạn chỉ có thể thêm hình dạng và tổ tiên của nó. Tôi nghĩ rằng có thể những gì bạn muốn là

List<? extends Shape> shapeSuper = new ArrayList<Shape>(); 

Chỉ định "mở rộng" có nghĩa là Hình dạng và bất kỳ thứ gì có nguồn gốc từ Hình dạng. Chỉ định "siêu" có nghĩa là Hình dạng và Hình dạng bất kỳ sẽ xuất phát từ đó.

Không chắc chắn về (B), trừ khi Object không tiềm ẩn. Điều gì xảy ra nếu bạn khai báo rõ ràng Hình dạng là public class Shape extends Object?

+0

Vấn đề với (B) là vì bạn không thể thêm nội dung vào vùng chứa chung được khai báo với tham số ký tự đại diện. –

+0

Thực ra, tôi đã sai. add (new Shape()) hoạt động tốt. Vấn đề là một chút gai góc hơn thế. –

17

Các "Nhận và Đặt Nguyên tắc" trong phần (2.4) là một viên ngọc thực sự từ Java Generics and Collections:

Các Nhận và Đặt Nguyên tắc: sử dụng một mở rộng ký tự đại diện khi bạn chỉ nhận được giá trị ra khỏi một cấu trúc , sử dụng ký tự đại diện siêu khi bạn chỉ đặt giá trị vào cấu trúc và không sử dụng ký tự đại diện khi cả hai đều nhận và đặt.

alt text http://oreilly.com/catalog/covers/0596527756_cat.gif

Ngoài ra, tuyên bố một loại như List<? super Shape> shapeSuper là hình thức loại xấu vì nó làm hạn chế việc sử dụng nó. Nói chung, thời gian duy nhất mà tôi sử dụng thẻ hoang dã là trong chữ ký phương pháp:

public void foo(List<? super Shape> shapeSuper) 
+3

Điều này còn được gọi là nguyên tắc PECS (Hiệu quả Java 2nd Ed.). "Nhà sản xuất mở rộng, siêu tiêu dùng". Nếu tham số bạn chấp nhận là nhà sản xuất, hãy sử dụng 'mở rộng'. –

+0

@Julien bạn nói để sử dụng ** super ** khi chúng tôi đặt giá trị. Tại sao chúng ta lại muốn đúc siêu lớp X thành X và sau đó đặt chúng vào danh sách X? Lợi thế của việc sử dụng siêu ở tất cả là gì? Tại sao không chỉ đơn giản là không sử dụng một ký tự đại diện bất kể put/get/put + get? – Pacerier

+0

@Pacerier này có thể là một câu hỏi cũ nhưng tôi nghĩ đọc sách hiệu quả java (2008) sẽ giúp bạn trả lời câu hỏi của bạn. – KyelJmD

0

Trong tham chiếu đến ở trên, tôi không nghĩ rằng đây là đúng:

bằng cách tuyên bố shapeSuper như List<? super Shape> shapeSuper, bạn có nói rằng nó thể chấp nhận bất kỳ đối tượng đó là một lớp siêu của Shape

đó dường như trực giác ở cái nhìn đầu tiên, nhưng thực sự tôi không nghĩ rằng đó là cách nó hoạt động. Bạn không thể chèn chỉ bất kỳ siêu lớp của hình dạng vào hình dạngSuper. superShape thực sự là một tham chiếu đến một List có thể bị hạn chế để giữ một supertype cụ thể (nhưng không được chỉ định) của Shape.

Cho phép tưởng tượng Hình dạng thực hiện có thể xem và có thể vẽ. Vì vậy, trong trường hợp này tham chiếu superShape thực sự có thể trỏ đến một số List<Viewable> hoặc List<Drawable> (hoặc thực sự là List<Object>) - nhưng chúng tôi không biết cái nào. Nếu nó thực sự là một List<Viewable> bạn không thể chèn một cá thể Drawable vào nó - và trình biên dịch sẽ ngăn cản bạn làm điều này.

Cấu trúc giới hạn dưới thực sự rất hữu ích trong việc làm cho các lớp chung trở nên linh hoạt hơn. Trong ví dụ sau nó cho phép chúng ta vượt qua vào phương pháp addShapeToSet một Set định nghĩa để chứa bất kỳ lớp cha của Shape - và chúng tôi vẫn có thể chèn một Shape vào nó:

public void addShapeToSet(Set<? super Shape> set) { 
    set.add(new Shape()); 
} 

public void testAddToSet() { 
    //these all work fine, because Shape implements all of these: 
    addShapeToSet(new HashSet<Viewable>()); 
    addShapeToSet(new HashSet<Drawable>());   
    addShapeToSet(new HashSet<Shape>());   
    addShapeToSet(new HashSet<Object>()); 
} 
25

Ví dụ, bạn có thể sử dụng một đồng bằng List<Shape> như Dan và Paul nói; bạn không cần sử dụng cú pháp dấu hỏi ký tự đại diện như List<? super Shape> hoặc List<? extends Shape>). Tôi nghĩ câu hỏi cơ bản của bạn có thể là, "khi nào tôi sẽ sử dụng một trong những tờ khai kiểu dấu hỏi?" (Nguyên tắc Nhận và Đặt mà Julien trích dẫn là một câu trả lời tuyệt vời cho câu hỏi này, nhưng tôi không nghĩ rằng nó có ý nghĩa rất nhiều trừ khi bạn nhìn thấy nó trong ngữ cảnh của một ví dụ.) Dưới đây là của tôi tại một phiên bản mở rộng của Get và Đặt nguyên tắc cho khi sử dụng ký tự đại diện.

Sử dụng <? extends T> nếu ...

  • Một phương pháp có một tham số lớp generic Foo<T> readSource
  • Method lấy trường hợp của T từ readSource, và không quan tâm nếu đối tượng thực tế lấy ra thuộc về một lớp con của T.

Sử dụng <? super T> nếu ...

  • một phương pháp có một tham số lớp generic Foo<T> writeDest
  • phương pháp này đặt trường hợp của T vào writeDest, và không quan tâm nếu writeDest cũng chứa các đối tượng đó là lớp con của T.

Dưới đây là một hương của một ví dụ cụ thể để minh họa những suy nghĩ đằng sau ký tự đại diện. Hãy tưởng tượng bạn đang viết một phương thức processSquare để loại bỏ một hình vuông từ một danh sách, xử lý nó và lưu trữ kết quả trong một danh sách đầu ra. Dưới đây là một phương pháp chữ ký:

void processSquare(List<Square> iSqua, List<Square> oSqua) 
{ Square s = iSqua.remove(0); s.doSquare(); oSqua.add(s); } 

Bây giờ bạn tạo một danh sách các DoubleSquares, mà mở rộng Square, và cố gắng xử lý chúng:

List<DoubleSquare> dsqares = ... 
List<Square> processed = new ArrayList<Square>; 
processSquare(dsqares, processed); // compiler error! dsquares is not List<Square> 

Trình biên dịch không thành công với một lỗi vì loại dsquares List<DoubleSquare> doesn không khớp loại tham số đầu tiên với processSquare, List<Square>. Có lẽ một DoubleSquare là một Square, nhưng bạn cần phải nói với trình biên dịch rằng List<DoubleSquare> là một List<Square> cho các mục đích của phương thức processSquare của bạn. Sử dụng ký tự đại diện <? extends Square> để thông báo cho trình biên dịch biết rằng phương thức của bạn có thể lấy Danh sách bất kỳ lớp con nào của Square.

void processSquare(List<? extends Square> iSqua, List<Square> oSqua) 

Tiếp theo bạn cải thiện ứng dụng để xử lý Vòng kết nối cũng như Hình vuông. Bạn muốn tổng hợp tất cả các hình dạng xử lý của bạn trong một danh sách duy nhất trong đó bao gồm cả các vòng tròn và hình vuông, vì vậy bạn thay đổi kiểu danh sách chế biến từ một List<Square> đến một List<Shape>:

List<DoubleSquare> dsqares = ... 
List<Circle> circles = ... 
List<Shape> processed = new ArrayList<Square>; 
processSquare(dsqares, processed); // compiler error! processed is not List<Square> 

Trình biên dịch không thành công với một lỗi mới. Bây giờ loại danh sách được xử lý List<Shape> không khớp với thông số thứ 2 để xử lýSquare, List<Square>. Sử dụng ký tự đại diện <? super Square> để thông báo cho trình biên dịch rằng một tham số nhất định có thể là Danh sách của bất kỳ lớp học siêu hạng nào của Square.

void processSquare(List<? extends Square> iSqua, 
          List<? super Square> oSqua) 

Đây là mã nguồn đầy đủ cho ví dụ. Đôi khi tôi tìm thấy nó dễ dàng hơn để tìm hiểu các công cụ bằng cách bắt đầu với một ví dụ làm việc và sau đó phá vỡ nó để xem làm thế nào trình biên dịch phản ứng.

package wild; 

import java.util.ArrayList; 
import java.util.LinkedList; 
import java.util.List; 

public abstract class Main { 
    // In processing the square, 
    // I'll take for input any type of List that can PRODUCE (read) squares. 
    // I'll take for output any type of List that can ACCEPT (write) squares. 
    static void processSquare(List<? extends Square> iSqua, List<? super Square> oSqua) 
    { Square s = iSqua.remove(0); s.doSquare(); oSqua.add(s); } 

    static void processCircle(List<? extends Circle> iCirc, List<? super Circle> oCirc) 
    { Circle c = iCirc.remove(0); c.doCircle(); oCirc.add(c); } 

    public static void main(String[] args) { 
    // Load some inputs 
    List<Circle> circles = makeList(new Circle()); 
    List<DoubleSquare> dsqares = makeList(new DoubleSquare()); 

    // Collated storage for completed shapes 
    List<Shape> processed = new ArrayList<Shape>(); 

    // Process the shapes 
    processSquare(dsqares, processed); 
    processCircle(circles, processed); 

    // Do post-processing 
    for (Shape s : processed) 
     s.shapeDone(); 
    } 

    static class Shape { void shapeDone() { System.out.println("Done with shape."); } } 
    static class Square extends Shape { void doSquare() { System.out.println("Square!"); } } 
    static class DoubleSquare extends Square {} 
    static class Circle extends Shape { void doCircle() { System.out.println("Circle!"); } } 

    static <T> List<T> makeList(T a) { 
    List<T> list = new LinkedList<T>(); list.add(a); return list; 
    } 

} 
+2

Được giải thích rõ ràng. –

+1

@ ThisIsTheDave Vậy sự khác nhau giữa ** ** và ** **? – Pacerier

+3

Không đồng ý về bài đăng dài. Chắc chắn nó đã cho tôi một vài phút để đọc và tiêu hóa, nhưng tôi biết ơn vì lời giải thích chi tiết và thực tế. Tôi có một sự hiểu biết tốt hơn bây giờ hơn tôi đã làm trước đây. Nếu độc giả không chuẩn bị cho một vài phút để hiểu một số quy tắc khá phức tạp thì họ cũng có thể không bận tâm cố gắng hiểu nó. – mdma

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