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;
}
}
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