Sự khác biệt giữa Danh sách, Danh sách <?>, Danh sách <T>, Danh sách <E> và Danh sách <Đối tượng>


194

Sự khác nhau giữa là gì List, List<?>, List<T>, List<E>, và List<Object>?

1. Danh sách

List: là một loại nguyên, do đó không typesafe. Nó sẽ chỉ tạo ra một lỗi thời gian chạy khi truyền là xấu. Chúng tôi muốn một lỗi thời gian biên dịch khi dàn diễn viên tệ. Không nên sử dụng.

2. Danh sách <?>

List<?>là một ký tự đại diện không giới hạn. Nhưng tôi không chắc nó để làm gì? Tôi có thể in List<?>mà không có vấn đề:

public static void test(List<?> list){
    System.out.println(list);   // Works
}

Tại sao tôi không thể thêm các mục vào a List<?>?

public static void test(List<?> list){
    list.add(new Long(2));     // Error
    list.add("2");             // Error
    System.out.println(list);
}

3. Danh sách <T>

public static void test(List<T> list){   // T cannot be resolved
    System.out.println(list);
}

Tôi không hiểu cú pháp này. Tôi đã thấy một cái gì đó như thế này, và nó hoạt động:

public <T> T[] toArray(T[] a){
    return a;   
}

Đôi khi, tôi thấy <T>, hay <E>, hoặc <U>, <T,E>. Tất cả đều giống nhau hay chúng đại diện cho một cái gì đó khác nhau?

4. Danh sách <Đối tượng>

Điều này đưa ra lỗi "Phương thức test(List<Object>)không áp dụng cho đối số List<String>":

public static void test(List<Object> list){
    System.out.println(list);
}

Nếu tôi thử điều này thì tôi nhận được "Không thể truyền từ List<String>đến List<Object>":

test((List<Object>) names);

Tôi bị bối rối. Stringlà một lớp con của Object, vậy tại sao không phải List<String>là một lớp con của List<Object>?

Câu trả lời:


77

1) Đúng

2) Bạn có thể coi danh sách đó là danh sách "chỉ đọc", trong đó bạn không quan tâm đến loại mặt hàng. Ví dụ: được sử dụng bởi một phương thức trả về độ dài của danh sách.

3) T, E và U giống nhau, nhưng mọi người có xu hướng sử dụng ví dụ T cho loại, E cho Element, V cho giá trị và K cho khóa. Phương thức biên dịch nói rằng nó lấy một mảng của một kiểu nhất định và trả về một mảng cùng loại.

4) Bạn không thể trộn cam và táo. Bạn sẽ có thể thêm một Đối tượng vào danh sách Chuỗi của mình nếu bạn có thể chuyển danh sách chuỗi sang phương thức mong đợi danh sách đối tượng. (Và không phải tất cả các đối tượng là chuỗi)


2
+1 cho danh sách chỉ đọc trên 2. Tôi viết một số mã để chứng minh điều này 2. tyvm
Thang Phạm

Tại sao mọi người sẽ sử dụng List<Object>cho?
Thắng Phạm

3
Đó là một cách để tạo một danh sách chấp nhận bất kỳ loại mặt hàng nào, bạn hiếm khi sử dụng nó.
Kaj

Trên thực tế tôi sẽ không nghĩ ai sẽ sử dụng nó vì bạn không thể có một định danh nào cả.
if_zero_equals_one

1
@if_zero_equals_one Có, nhưng sau đó bạn sẽ nhận được cảnh báo trình biên dịch (nó sẽ cảnh báo và nói rằng bạn đang sử dụng các loại thô) và bạn không bao giờ muốn biên dịch mã của mình bằng các cảnh báo.
Kaj

26

Đối với phần cuối cùng: Mặc dù Chuỗi là tập con của Đối tượng, nhưng Danh sách <Chuỗi> không được kế thừa từ Danh sách <Đối tượng>.


11
Điểm rất tốt; nhiều người cho rằng vì lớp C kế thừa từ lớp P, nên Danh sách <C> đó cũng kế thừa từ Danh sách <P>. Như bạn đã chỉ ra đây không phải là trường hợp. Lý do là nếu chúng ta có thể chuyển từ Danh sách <Chuỗi> sang Danh sách <Đối tượng>, thì chúng ta có thể đưa Đối tượng vào danh sách đó, do đó vi phạm hợp đồng ban đầu của Danh sách <Chuỗi> khi cố gắng truy xuất phần tử.
Peter

2
+1. Điểm tốt là tốt. Vậy tại sao mọi người sẽ sử dụng List<Object>cho?
Thắng Phạm

9
Danh sách <Object> có thể được sử dụng để lưu trữ danh sách các đối tượng của các lớp khác nhau.
Farshid Zaker

20

Ký hiệu này List<?>có nghĩa là "một danh sách của một cái gì đó (nhưng tôi không nói những gì)". Vì mã trong testhoạt động cho bất kỳ loại đối tượng nào trong danh sách, nên mã này hoạt động như một tham số phương thức chính thức.

Sử dụng một tham số loại (như trong điểm 3 của bạn), yêu cầu tham số loại được khai báo. Cú pháp Java cho điều đó là đặt <T>trước hàm. Điều này hoàn toàn tương tự với việc khai báo tên tham số chính thức cho một phương thức trước khi sử dụng các tên trong thân phương thức.

Về việc List<Object>không chấp nhận a List<String>, điều đó có ý nghĩa bởi vì a Stringlà không Object; nó là một lớp con của Object. Cách khắc phục là khai báo public static void test(List<? extends Object> set) .... Nhưng sau đó extends Objectlà dư thừa, bởi vì mỗi lớp trực tiếp hoặc gián tiếp mở rộng Object.


Tại sao mọi người sẽ sử dụng List<Object>cho?
Thắng Phạm

10
Tôi nghĩ rằng "một danh sách của một cái gì đó" là một ý nghĩa tốt hơn List<?>vì danh sách này là một số loại cụ thể nhưng không rõ. List<Object>sẽ thực sự là "một danh sách của bất cứ thứ gì" vì nó thực sự có thể chứa bất cứ thứ gì.
ColinD

1
@ColinD - Tôi có nghĩa là "bất cứ điều gì" theo nghĩa "bất kỳ một điều". Nhưng bạn nói đúng; nó có nghĩa là "một danh sách của một cái gì đó, nhưng tôi sẽ không cho bạn biết những gì".
Ted Hopp

@ColinD có nghĩa là tại sao bạn lặp lại lời nói của anh ấy? vâng, nó được viết với một vài từ khác nhau, nhưng ý nghĩa thì giống nhau ...
user25

14

Lý do bạn không thể đưa List<String>ra List<Object>là nó sẽ cho phép bạn vi phạm các ràng buộc của List<String>.

Hãy suy nghĩ về kịch bản sau đây: Nếu tôi có một List<String>, nó được cho là chỉ chứa các đối tượng thuộc loại String. (Đó là một finallớp học)

Nếu tôi có thể bỏ nó thành a List<Object>, thì điều đó cho phép tôi thêm Objectvào danh sách đó, do đó vi phạm hợp đồng ban đầu List<String>.

Vì vậy, nói chung, nếu lớp Ckế thừa từ lớp P, bạn không thể nói rằng GenericType<C>cũng kế thừa từ GenericType<P>.

NB Tôi đã nhận xét về điều này trong một câu trả lời trước nhưng muốn mở rộng về nó.


tyvm, tôi tải lên cả bình luận của bạn và câu trả lời của bạn, vì đó là một lời giải thích rất tốt. Bây giờ mọi người sẽ sử dụng ở đâu và tại sao List<Object>?
Thắng Phạm

3
Nói chung, bạn không nên sử dụng List<Object>vì nó đánh bại mục đích của thuốc generic. Tuy nhiên, có những trường hợp mã cũ có thể Listchấp nhận các loại khác nhau, vì vậy bạn có thể muốn trang bị thêm mã để sử dụng tham số loại chỉ để tránh cảnh báo trình biên dịch cho các loại thô. (Nhưng chức năng không thay đổi)
Peter


5

Chúng ta hãy nói về chúng trong bối cảnh lịch sử Java;

  1. List:

Danh sách có nghĩa là nó có thể bao gồm bất kỳ Đối tượng. Danh sách đã được phát hành trước Java 5.0; Danh sách giới thiệu Java 5.0, để tương thích ngược.

List list=new  ArrayList();
list.add(anyObject);
  1. List<?>:

?có nghĩa là đối tượng chưa biết không phải đối tượng nào; ?giới thiệu ký tự đại diện là để giải quyết vấn đề được xây dựng bởi Loại chung; xem ký tự đại diện ; nhưng điều này cũng gây ra một vấn đề khác:

Collection<?> c = new ArrayList<String>();
c.add(new Object()); // Compile time error
  1. List< T> List< E>

Có nghĩa là Tuyên bố chung tại tiền đề không có loại T hoặc E nào trong dự án Lib của bạn.

  1. List< Object> có nghĩa là tham số hóa chung.

5

Trong điểm thứ ba của bạn, "T" không thể được giải quyết vì nó không được khai báo, thông thường khi bạn khai báo một lớp chung, bạn có thể sử dụng "T" làm tên của tham số loại ràng buộc , nhiều ví dụ trực tuyến bao gồm hướng dẫn của oracle sử dụng "T" làm Tên của tham số loại, ví dụ, bạn khai báo một lớp như:

public class FooHandler<T>
{
   public void operateOnFoo(T foo) { /*some foo handling code here*/}

}

bạn đang nói rằng FooHandler's operateOnFoophương thức đó mong đợi một biến kiểu "T" được khai báo trên chính khai báo lớp, với ý nghĩ này, sau này bạn có thể thêm một phương thức khác như

public void operateOnFoos(List<T> foos)

trong tất cả các trường hợp T, E hoặc U có tất cả các định danh của tham số loại, bạn thậm chí có thể có nhiều hơn một tham số loại sử dụng cú pháp

public class MyClass<Atype,AnotherType> {}

trong ponint của bạn mặc dù hoàn toàn Sting là một loại đối tượng phụ, trong các lớp khái quát không có mối quan hệ như vậy, List<String>không phải là một loại phụ của List<Object>chúng là hai loại khác nhau từ quan điểm của trình biên dịch, điều này được giải thích tốt nhất trong mục blog này


5

Học thuyết

String[] có thể được đúc Object[]

nhưng

List<String>không thể được đúc tới List<Object>.

Thực hành

Đối với các danh sách, nó tinh tế hơn thế, bởi vì tại thời điểm biên dịch , loại tham số Danh sách được truyền cho một phương thức không được kiểm tra. Định nghĩa phương thức cũng có thể nói List<?>- theo quan điểm của người biên dịch, nó là tương đương. Đây là lý do tại sao ví dụ số 2 của OP đưa ra lỗi thời gian chạy không phải là lỗi biên dịch.

Nếu bạn xử lý một List<Object>tham số được truyền cho một phương thức một cách cẩn thận để bạn không buộc kiểm tra kiểu trên bất kỳ phần tử nào của danh sách, thì bạn có thể xác định phương thức của mình bằng cách sử dụng List<Object>nhưng thực tế chấp nhận một List<String>tham số từ mã gọi.

A. Vì vậy, mã này sẽ không cung cấp lỗi biên dịch hoặc thời gian chạy và thực sự (và có thể đáng ngạc nhiên?) Hoạt động:

public static void main(String[] args) {
    List argsList = new ArrayList<String>();
    argsList.addAll(Arrays.asList(args));
    test(argsList);  // The object passed here is a List<String>
}

public static void test(List<Object> set) {
    List<Object> params = new ArrayList<>();  // This is a List<Object>
    params.addAll(set);       // Each String in set can be added to List<Object>
    params.add(new Long(2));  // A Long can be added to List<Object>
    System.out.println(params);
}

B. Mã này sẽ đưa ra lỗi thời gian chạy:

public static void main(String[] args) {
    List argsList = new ArrayList<String>();
    argsList.addAll(Arrays.asList(args));
    test1(argsList);
    test2(argsList);
}

public static void test1(List<Object> set) {
    List<Object> params = set;  // Surprise!  Runtime error
}

public static void test2(List<Object> set) {
    set.add(new Long(2));       // Also a runtime error
}

C. Mã này sẽ đưa ra lỗi thời gian chạy ( java.lang.ArrayStoreException: java.util.Collections$UnmodifiableRandomAccessList Object[]):

public static void main(String[] args) {
    test(args);
}

public static void test(Object[] set) {
    Object[] params = set;    // This is OK even at runtime
    params[0] = new Long(2);  // Surprise!  Runtime error
}

Trong B, tham số setkhông phải là một kiểu gõ Listvào thời gian biên dịch: trình biên dịch xem nó là List<?>. Có lỗi thời gian chạy vì tại thời gian chạy, settrở thành đối tượng thực tế được truyền từ main()đó và đó là một List<String>. A List<String>không thể được đúc List<Object>.

Trong C, tham số setyêu cầu một Object[]. Không có lỗi biên dịch và không có lỗi thời gian chạy khi nó được gọi với một String[]đối tượng là tham số. Đó là bởi vì String[]diễn viên Object[]. Nhưng đối tượng thực tế nhận được test()vẫn là một String[], nó đã không thay đổi. Vậy paramsđối tượng cũng trở thành a String[]. Và phần tử 0 của a String[]không thể được gán cho a Long!

(Hy vọng tôi có mọi thứ ngay tại đây, nếu suy luận của tôi sai. Tôi chắc chắn cộng đồng sẽ nói với tôi. CẬP NHẬT: Tôi đã cập nhật mã trong ví dụ A để nó thực sự biên dịch, trong khi vẫn hiển thị điểm được thực hiện.)


Tôi đã thử ví dụ của bạn A nó không hoạt động : List<Object> cannot be applied to List<String>. Bạn không thể vượt qua ArrayList<String>một phương pháp mong đợi ArrayList<Object>.
phân tích cú pháp

Cảm ơn, khá muộn trong ngày tôi đã điều chỉnh ví dụ A để bây giờ nó hoạt động. Thay đổi chính là định nghĩa argsList một cách tổng quát trong hàm main ().
radfast

4

Vấn đề 2 là OK, vì "System.out.println (set);" có nghĩa là "System.out.println (set.toString ());" set là một thể hiện của List, do đó, trình biên dịch sẽ gọi List.toString ();

public static void test(List<?> set){
set.add(new Long(2)); //--> Error  
set.add("2");    //--> Error
System.out.println(set);
} 
Element ? will not promise Long and String, so complier will  not accept Long and String Object

public static void test(List<String> set){
set.add(new Long(2)); //--> Error
set.add("2");    //--> Work
System.out.println(set);
}
Element String promise it a String, so complier will accept String Object

Vấn đề 3: các ký hiệu này giống nhau, nhưng bạn có thể cung cấp cho chúng thông số kỹ thuật differet. Ví dụ:

public <T extends Integer,E extends String> void p(T t, E e) {}

Bài toán 4: Bộ sưu tập không cho phép hiệp phương sai kiểu. Nhưng mảng không cho phép hiệp phương sai.


0

Bạn nói đúng: Chuỗi là tập con của Object. Vì String "chính xác" hơn Object, nên bạn nên sử dụng nó làm đối số cho System.out.println ().

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.