Java: ký tự đại diện bị giới hạn hoặc tham số kiểu giới hạn?


82

Gần đây, tôi đã đọc bài viết này: http://download.oracle.com/javase/tutorial/extra/generics/wildcards.html

Câu hỏi của tôi là, thay vì tạo một phương thức như thế này:

public void drawAll(List<? extends Shape> shapes){
    for (Shape s: shapes) {
        s.draw(this);
    }
}

Tôi có thể tạo một phương thức như thế này và nó hoạt động tốt:

public <T extends Shape> void drawAll(List<T> shapes){
    for (Shape s: shapes) {
        s.draw(this);
    }
}

Tôi nên sử dụng cách nào? Ký tự đại diện có hữu ích trong trường hợp này không?


? là một ký hiệu viết tắt; trình biên dịch nội bộ vẫn thay thế nó bằng một tham số kiểu; khi có lỗi trình biên dịch, bạn sẽ thấy tham số kiểu thay thế, thay vì?
chối cãi được

9
đính chính: nhận xét trước đây của tôi là SAI. ký tự đại diện phức tạp hơn tôi nghĩ.
chối cãi

@irreputable trong khi nó thực sự phức tạp hơn, quan điểm của bạn về việc thay thế nó bằng một tham số kiểu là hoàn toàn hợp lệ; nó chỉ là một cái mà bạn không thể khai báo.
Eugene

nếu chúng ta nhìn vào chỉ hai phương pháp này vì họ là - không có sự khác biệt và chỉ là vấn đề của phong cách; khác (nếu bạn thêm một số thông số chung chung hơn), mọi thứ sẽ thay đổi
Eugene

Câu trả lời:


133

Nó phụ thuộc vào những gì bạn cần làm. Bạn cần sử dụng tham số kiểu giới hạn nếu bạn muốn làm điều gì đó như sau:

public <T extends Shape> void addIfPretty(List<T> shapes, T shape) {
    if (shape.isPretty()) {
       shapes.add(shape);
    }
}

Ở đây chúng ta có a List<T> shapesvà a T shape, do đó chúng ta có thể an toàn shapes.add(shape). Nếu nó đã được khai báo List<? extends Shape>, bạn KHÔNG thể an toàn addvới nó (vì bạn có thể có a List<Square>và a Circle).

Vì vậy, bằng cách đặt tên cho tham số kiểu bị giới hạn, chúng ta có tùy chọn sử dụng nó ở nơi khác trong phương thức chung của chúng ta. Tất nhiên, thông tin này không phải lúc nào cũng bắt buộc, vì vậy nếu bạn không cần biết nhiều về loại (ví dụ: của bạn drawAll), thì chỉ cần ký tự đại diện là đủ.

Ngay cả khi bạn không tham chiếu lại tham số kiểu bị giới hạn, một tham số kiểu bị giới hạn vẫn được yêu cầu nếu bạn có nhiều giới hạn. Đây là trích dẫn từ Câu hỏi thường gặp về Java Generics của Angelika Langer

Sự khác biệt giữa giới hạn ký tự đại diện và tham số kiểu bị ràng buộc là gì?

Một ký tự đại diện chỉ có thể có một giới hạn, trong khi một tham số kiểu có thể có nhiều giới hạn. Một ký tự đại diện có thể có giới hạn dưới hoặc giới hạn trên, trong khi không có cái gọi là giới hạn dưới cho tham số kiểu.

Giới hạn ký tự đại diện và giới hạn tham số kiểu thường bị nhầm lẫn, vì chúng đều được gọi là giới hạn và có một phần cú pháp tương tự. […]

Cú pháp :

  type parameter bound     T extends Class & Interface1 & … & InterfaceN

  wildcard bound  
      upper bound          ? extends SuperType
      lower bound          ? super   SubType

Một ký tự đại diện chỉ có thể có một giới hạn, giới hạn dưới hoặc giới hạn trên. Danh sách các giới hạn ký tự đại diện không được phép.

Một tham số kiểu, trong các ràng buộc, có thể có một số giới hạn, nhưng không có cái gọi là giới hạn dưới cho một tham số kiểu.

Trích dẫn từ Phiên bản Java thứ 2 hiệu quả, Mục 28: Sử dụng các ký tự đại diện có giới hạn để tăng tính linh hoạt của API :

Để có tính linh hoạt tối đa, hãy sử dụng các loại ký tự đại diện trên các tham số đầu vào đại diện cho nhà sản xuất hoặc người tiêu dùng. […] PECS là viết tắt của từ sản xuất extends, người tiêu dùng- super[…]

Không sử dụng các loại ký tự đại diện làm loại trả về . Thay vì cung cấp thêm tính linh hoạt cho người dùng của bạn, nó sẽ buộc họ sử dụng các loại ký tự đại diện trong mã khách hàng. Được sử dụng đúng cách, các loại ký tự đại diện gần như vô hình đối với người dùng của một lớp. Chúng tạo ra các phương thức chấp nhận các tham số mà chúng nên chấp nhận và từ chối những tham số mà chúng nên loại bỏ. Nếu người dùng của lớp phải suy nghĩ về các loại ký tự đại diện, có thể có gì đó sai với API của lớp .

Áp dụng nguyên tắc PECS, bây giờ chúng ta có thể quay lại addIfPrettyví dụ của mình và làm cho nó linh hoạt hơn bằng cách viết như sau:

public <T extends Shape> void addIfPretty(List<? super T> list, T shape) { … }

Bây giờ chúng ta có thể addIfPretty, nói, a Circle, để a List<Object>. Đây rõ ràng là an toàn kiểu chữ, nhưng khai báo ban đầu của chúng tôi không đủ linh hoạt để cho phép nó.

Câu hỏi liên quan


Tóm lược

  • Sử dụng các tham số / ký tự đại diện kiểu giới hạn, chúng làm tăng tính linh hoạt của API của bạn
  • Nếu kiểu yêu cầu nhiều tham số, bạn không có lựa chọn nào khác ngoài việc sử dụng tham số kiểu bị giới hạn
  • nếu loại yêu cầu giới hạn thấp hơn, bạn không có lựa chọn nào khác ngoài sử dụng ký tự đại diện giới hạn
  • "Nhà sản xuất" có giới hạn trên, "người tiêu dùng" có tỷ lệ thấp hơn
  • Không sử dụng ký tự đại diện trong các loại trả lại

Cảm ơn bạn rất nhiều, lời giải thích của bạn là rất rõ ràng và bài học
Tony Lê

1
@Tony: Cuốn sách cũng có một cuộc thảo luận ngắn về cách chọn giữa ký tự đại diện và tham số kiểu khi không có ràng buộc. Về cơ bản, nếu tham số kiểu chỉ xuất hiện một lần trong khai báo phương thức, hãy sử dụng ký tự đại diện. Xem thêm reverse(List<?>)ví dụ từ JLS java.sun.com/docs/books/jls/third_edition/html/…
polygenelubricants

Tôi nhận được ngoại lệ UnsupportedOperationException cho đoạn mã sau, <code> public static <T kéo dài Số> void add (List <? Super T> list, T num) {list.add (num); } </code>
Samra

1
Ngoài ra - bạn không thể chuyển ?dưới dạng một tham số phương thức doWork(? type)vì khi đó bạn không thể sử dụng tham số đó trong phương thức. Bạn cần sử dụng tham số kiểu.
Tomasz Mularczyk

1
@VivekVardhan, bạn không thể tạo một phương thức như vậy bằng cách sử dụng ký tự đại diện, đó là vấn đề. Khi quyết định sử dụng cái nào, đây là một trong những nguồn giải thích tốt nhất.
Eugene

6

Trong ví dụ của bạn, bạn không thực sự cần sử dụng T, vì bạn không sử dụng kiểu đó ở bất kỳ nơi nào khác.

Nhưng nếu bạn đã làm điều gì đó như:

public <T extends Shape> T drawFirstAndReturnIt(List<T> shapes){
    T s = shapes.get(0);
    s.draw(this);
    return s;
}

hoặc như polygenlubricants đã nói, nếu bạn muốn đối sánh tham số kiểu trong danh sách với tham số kiểu khác:

public <T extends Shape> void mergeThenDraw(List<T> shapes1, List<T> shapes2) {
    List<T> mergedList = new ArrayList<T>();
    mergedList.addAll(shapes1);
    mergedList.addAll(shapes2);
    for (Shape s: mergedList) {
        s.draw(this);
    }
}

Trong ví dụ đầu tiên, bạn có thêm một chút an toàn kiểu, sau đó chỉ trả về Hình dạng, vì sau đó bạn có thể chuyển kết quả cho một hàm có thể lấy một con của Hình dạng. Ví dụ: bạn có thể chuyển một List<Square>phương thức của tôi, và sau đó chuyển Hình vuông kết quả cho một phương thức chỉ nhận Hình vuông. Nếu bạn đã sử dụng '?' bạn sẽ phải truyền Hình dạng kết quả thành Hình vuông sẽ không được nhập an toàn.

Trong ví dụ thứ hai, bạn đảm bảo rằng cả hai danh sách đều có cùng một tham số kiểu (mà bạn không thể thực hiện với '?', Vì mỗi '?' Là khác nhau), để bạn có thể tạo danh sách chứa tất cả các phần tử từ cả hai. .


Tôi tin là Shape s = ...nên T s = ..., nếu không thì không return s;nên biên dịch.
polygenelubricants,

1

Hãy xem xét ví dụ sau từ Lập trình Java của James Gosling ấn bản thứ 4 bên dưới, nơi chúng tôi muốn hợp nhất 2 SinglyLinkQueue:

public static <T1, T2 extends T1> void merge(SinglyLinkQueue<T1> d, SinglyLinkQueue<T2> s){
    // merge s element into d
}

public static <T> void merge(SinglyLinkQueue<T> d, SinglyLinkQueue<? extends T> s){
        // merge s element into d
}

Cả hai phương pháp trên đều có chức năng giống nhau. Vì vậy, cái nào là tốt hơn? Câu trả lời là câu thứ hai. Theo lời của chính tác giả:

"Quy tắc chung là sử dụng ký tự đại diện khi bạn có thể vì mã có ký tự đại diện thường dễ đọc hơn mã có nhiều tham số kiểu. Khi quyết định xem bạn có cần một biến kiểu hay không, hãy tự hỏi xem biến kiểu đó có được sử dụng để liên kết hai hay nhiều tham số không, hoặc để liên kết kiểu tham số với kiểu trả về. Nếu câu trả lời là không, thì ký tự đại diện là đủ. "

Lưu ý: Trong sách chỉ đưa ra phương thức thứ hai và tên tham số kiểu là S thay vì 'T'. Phương pháp đầu tiên không có trong cuốn sách.


1

Theo như tôi hiểu, ký tự đại diện cho phép mã ngắn gọn hơn trong các trường hợp không yêu cầu tham số kiểu (ví dụ: vì nó được tham chiếu ở một số nơi hoặc vì nhiều giới hạn được yêu cầu như chi tiết trong các câu trả lời khác).

Trong liên kết mà bạn cho biết tôi đã đọc (trong "Phương pháp Chung") các câu lệnh sau gợi ý theo hướng này:

Các phương thức chung cho phép các tham số kiểu được sử dụng để thể hiện sự phụ thuộc giữa các kiểu của một hoặc nhiều đối số đối với một phương thức và / hoặc kiểu trả về của nó. Nếu không có sự phụ thuộc như vậy, không nên sử dụng phương pháp chung.

[...]

Sử dụng ký tự đại diện rõ ràng và ngắn gọn hơn so với khai báo các tham số kiểu rõ ràng, và do đó nên được ưu tiên hơn bất cứ khi nào có thể.

[...]

Các ký tự đại diện cũng có lợi thế là chúng có thể được sử dụng bên ngoài chữ ký phương thức, như các loại trường, biến cục bộ và mảng.


0

Cách thứ hai dài dòng hơn một chút, nhưng nó cho phép bạn tham khảo Tbên trong nó:

for (T shape : shapes) {
    ...
}

Đó là sự khác biệt duy nhất, theo như tôi hiểu.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.