Tôi bắt gặp mã Java như thế này:
public interface Foo<E> {}
public interface Bar<T> {}
public interface Zar<?> {}
Sự khác biệt giữa cả ba điều trên và họ gọi loại khai báo lớp hoặc giao diện này trong Java là gì?
Tôi bắt gặp mã Java như thế này:
public interface Foo<E> {}
public interface Bar<T> {}
public interface Zar<?> {}
Sự khác biệt giữa cả ba điều trên và họ gọi loại khai báo lớp hoặc giao diện này trong Java là gì?
Câu trả lời:
Chà, không có sự khác biệt giữa hai cái đầu tiên - chúng chỉ sử dụng các tên khác nhau cho tham số loại ( E
hoặc T
).
Thứ ba không phải là một khai báo hợp lệ - ?
được sử dụng làm ký tự đại diện được sử dụng khi cung cấp một đối số loại , ví dụ: List<?> foo = ...
có nghĩa là foo
đề cập đến một danh sách một số loại, nhưng chúng ta không biết gì.
Tất cả điều này là khái quát , đó là một chủ đề khá lớn. Bạn có thể muốn tìm hiểu về nó thông qua các tài nguyên sau, mặc dù tất nhiên có sẵn nhiều hơn:
T
và E
- chúng chỉ là định danh. Bạn có thể viết KeyValuePair<K, V>
ví dụ. ?
có ý nghĩa đặc biệt mặc dù.
Đó là quy ước hơn bất cứ điều gì khác.
T
có nghĩa là một loại E
có nghĩa là một yếu tố ( List<E>
: danh sách các yếu tố) K
là khóa (trong một Map<K,V>
) V
là Giá trị (dưới dạng giá trị trả về hoặc giá trị được ánh xạ) Chúng hoàn toàn có thể hoán đổi cho nhau (xung đột trong cùng một tuyên bố mặc dù).
Các câu trả lời trước giải thích các tham số loại (T, E, v.v.), nhưng không giải thích ký tự đại diện, "?" Hoặc sự khác biệt giữa chúng, vì vậy tôi sẽ giải quyết điều đó.
Đầu tiên, chỉ cần rõ ràng: các tham số ký tự đại diện và loại không giống nhau. Trong đó các tham số loại xác định một loại biến (ví dụ: T) đại diện cho loại cho phạm vi, ký tự đại diện không: ký tự đại diện chỉ xác định một tập hợp các loại cho phép mà bạn có thể sử dụng cho loại chung. Không có bất kỳ giới hạn ( extends
hoặc super
) nào, ký tự đại diện có nghĩa là "sử dụng bất kỳ loại nào ở đây".
Ký tự đại diện luôn nằm giữa các dấu ngoặc nhọn và nó chỉ có ý nghĩa trong ngữ cảnh của một loại chung:
public void foo(List<?> listOfAnyType) {...} // pass a List of any type
không bao giờ
public <?> ? bar(? someType) {...} // error. Must use type params here
hoặc là
public class MyGeneric ? { // error
public ? getFoo() { ... } // error
...
}
Nó trở nên khó hiểu hơn khi chúng chồng lên nhau. Ví dụ:
List<T> fooList; // A list which will be of type T, when T is chosen.
// Requires T was defined above in this scope
List<?> barList; // A list of some type, decided elsewhere. You can do
// this anywhere, no T required.
Có rất nhiều sự trùng lặp về những gì có thể với các định nghĩa phương thức. Sau đây là, về mặt chức năng, giống hệt nhau:
public <T> void foo(List<T> listOfT) {...}
public void bar(List<?> listOfSomething) {...}
Vì vậy, nếu có sự chồng chéo, tại sao sử dụng cái này hay cái kia? Đôi khi, đó thực sự chỉ là phong cách: một số người nói rằng nếu bạn không cần một loại param, bạn nên sử dụng ký tự đại diện chỉ để làm cho mã đơn giản hơn / dễ đọc hơn. Một sự khác biệt chính mà tôi đã giải thích ở trên: params type xác định một biến loại (ví dụ: T) mà bạn có thể sử dụng ở nơi khác trong phạm vi; ký tự đại diện không. Mặt khác, có hai sự khác biệt lớn giữa các thông số loại và ký tự đại diện:
Loại params có thể có nhiều lớp giới hạn; ký tự đại diện không thể:
public class Foo <T extends Comparable<T> & Cloneable> {...}
Ký tự đại diện có thể có giới hạn thấp hơn; loại params không thể:
public void bar(List<? super Integer> list) {...}
Trong phần trên, List<? super Integer>
định nghĩa Integer
là giới hạn dưới của ký tự đại diện, có nghĩa là loại Danh sách phải là Số nguyên hoặc siêu loại Số nguyên. Giới hạn loại chung là vượt quá những gì tôi muốn đề cập chi tiết. Nói tóm lại, nó cho phép bạn xác định loại chung có thể là loại nào. Điều này làm cho nó có thể điều trị tổng quát đa hình. Ví dụ:
public void foo(List<? extends Number> numbers) {...}
Bạn có thể vượt qua một List<Integer>
, List<Float>
, List<Byte>
, vv cho numbers
. Không có giới hạn loại, điều này sẽ không hoạt động - đó chỉ là cách tạo ra thuốc generic.
Cuối cùng, đây là một định nghĩa phương thức sử dụng ký tự đại diện để làm điều gì đó mà tôi không nghĩ rằng bạn có thể làm bất kỳ cách nào khác:
public static <T extends Number> void adder(T elem, List<? super Number> numberSuper) {
numberSuper.add(elem);
}
numberSuper
có thể là Danh sách số hoặc bất kỳ siêu số nào (ví dụ List<Object>
:) và elem
phải là Số hoặc bất kỳ loại phụ nào. Với tất cả các giới hạn, trình biên dịch có thể chắc chắn rằng đó .add()
là loại an toàn.
Một biến loại, <T>, có thể là bất kỳ loại không nguyên thủy nào bạn chỉ định: bất kỳ loại lớp, bất kỳ loại giao diện, bất kỳ loại mảng hoặc thậm chí một biến loại khác.
Các tên tham số loại thường được sử dụng là:
Trong Java 7, nó được phép khởi tạo như thế này:
Foo<String, Integer> foo = new Foo<>(); // Java 7
Foo<String, Integer> foo = new Foo<String, Integer>(); // Java 6
Các tên tham số loại thường được sử dụng là:
E - Element (used extensively by the Java Collections Framework)
K - Key
N - Number
T - Type
V - Value
S,U,V etc. - 2nd, 3rd, 4th types
Bạn sẽ thấy những tên này được sử dụng trong suốt API Java SE
trình biên dịch sẽ tạo một bản chụp cho mỗi ký tự đại diện (ví dụ: dấu hỏi trong Danh sách) khi nó tạo thành một hàm như:
foo(List<?> list) {
list.put(list.get()) // ERROR: capture and Object are not identical type.
}
Tuy nhiên, một kiểu chung như V sẽ ổn và biến nó thành một phương thức chung :
<V>void foo(List<V> list) {
list.put(list.get())
}