Danh sách <Bản đồ <Chuỗi, Chuỗi >> so với Danh sách <? mở rộng Bản đồ <Chuỗi, Chuỗi >>


129

Có sự khác biệt nào giữa

List<Map<String, String>>

List<? extends Map<String, String>>

?

Nếu không có sự khác biệt, lợi ích của việc sử dụng là ? extendsgì?


2
Tôi yêu java nhưng đây là một trong những điều không tốt ...
Mite Mitreski 22/03/2016

4
Tôi cảm thấy rằng nếu chúng ta đọc nó như "bất cứ điều gì kéo dài ...", nó sẽ trở nên rõ ràng.
Rác

Thật đáng kinh ngạc, hơn 12 nghìn lượt xem trong khoảng 3 ngày? !!
Tiếng Anh.

5
Nó đạt đến trang nhất Hacker News. Chúc mừng.
r3st0r3

1
@ Eng.Found đây rồi. Không còn trên trang nhất, nhưng đã có ngày hôm qua. news.ycombinator.net/item?id=3751901 (hôm qua là giữa Chủ nhật ở đây tại Ấn Độ.)
r3st0r3 26/03 '

Câu trả lời:


180

Sự khác biệt là, ví dụ, một

List<HashMap<String,String>>

là một

List<? extends Map<String,String>>

nhưng không phải là một

List<Map<String,String>>

Vì thế:

void withWilds( List<? extends Map<String,String>> foo ){}
void noWilds( List<Map<String,String>> foo ){}

void main( String[] args ){
    List<HashMap<String,String>> myMap;

    withWilds( myMap ); // Works
    noWilds( myMap ); // Compiler error
}

Bạn sẽ nghĩ rằng một Listtrong HashMaps phải là một Listtrong Maps, nhưng có một lý do chính đáng tại sao nó không phải là:

Giả sử bạn có thể làm:

List<HashMap<String,String>> hashMaps = new ArrayList<HashMap<String,String>>();

List<Map<String,String>> maps = hashMaps; // Won't compile,
                                          // but imagine that it could

Map<String,String> aMap = Collections.singletonMap("foo","bar"); // Not a HashMap

maps.add( aMap ); // Perfectly legal (adding a Map to a List of Maps)

// But maps and hashMaps are the same object, so this should be the same as

hashMaps.add( aMap ); // Should be illegal (aMap is not a HashMap)

Vì vậy, đây là lý do tại sao một Listsố HashMaps không phải là một Listcủa Maps.


6
Tuy nhiên, HashMaplà một Mapdo đa hình.
Eng.Fouad

46
Đúng vậy, nhưng một Listsố HashMaps không phải là một Listcủa Maps.
chân thực

1
Điều này được gọi là định lượng giới hạn
Dan Burton

Ví dụ tốt. Cũng đáng chú ý là ngay cả khi bạn tuyên bố List<Map<String,String>> maps = hashMaps; HashMap<String,String> aMap = new HashMap<String, String>();, bạn vẫn sẽ thấy đó maps.add(aMap);là bất hợp pháp trong khi hashMaps.add(aMap);là hợp pháp. Mục đích là để ngăn chặn việc thêm các loại sai, nhưng nó sẽ không cho phép thêm đúng loại (trình biên dịch không thể xác định loại "đúng" trong thời gian biên dịch)
Raze

@Raze không, thực sự bạn có thể thêm một HashMapdanh sách Maps, cả hai ví dụ của bạn đều hợp pháp, nếu tôi đọc chúng chính xác.
chân thực

24

Bạn không thể gán biểu thức với các kiểu như List<NavigableMap<String,String>>đầu tiên.

(Nếu bạn muốn biết lý do tại sao bạn không thể chỉ định List<String>để List<Object>xem hàng trăm câu hỏi khác về SO.)


1
Có thể giải thích thêm không? tôi không thể hiểu hoặc bất kỳ liên kết cho thực hành tốt?
Samir Mangroliya

3
@Samir Giải thích cái gì? List<String>không phải là một kiểu con của List<Object>? - xem, ví dụ, stackoverflow.com/questions/3246137/ từ
Tom Hawtin - tackline

2
Điều này không giải thích sự khác biệt giữa có hoặc không có ? extends. Nó cũng không giải thích mối tương quan với siêu / tiểu loại hoặc co / chống chỉ định (nếu có).
Abel

16

Những gì tôi còn thiếu trong các câu trả lời khác là một tài liệu tham khảo về cách điều này liên quan đến đồng biến và chống chỉ định và siêu phụ (nghĩa là đa hình) nói chung và Java nói riêng. Điều này có thể được OP hiểu rõ, nhưng chỉ trong trường hợp, đây là:

Hiệp phương sai

Nếu bạn có một lớp Automobile, thì CarTrucklà kiểu con của chúng. Bất kỳ chiếc xe nào cũng có thể được gán cho một loại ô tô, điều này nổi tiếng trong OO và được gọi là đa hình. Hiệp phương sai đề cập đến việc sử dụng nguyên tắc tương tự này trong các tình huống với thuốc generic hoặc đại biểu. Java không có đại biểu (chưa), vì vậy thuật ngữ này chỉ áp dụng cho khái quát.

Tôi có xu hướng nghĩ về hiệp phương sai là đa hình tiêu chuẩn những gì bạn mong đợi để làm việc mà không cần suy nghĩ, bởi vì:

List<Car> cars;
List<Automobile> automobiles = cars;
// You'd expect this to work because Car is-a Automobile, but
// throws inconvertible types compile error.

Tuy nhiên, lý do của lỗi là chính xác: List<Car> không kế thừa từ List<Automobile>đó và do đó không thể được gán cho nhau. Chỉ các tham số loại chung có mối quan hệ kế thừa. Mọi người có thể nghĩ rằng trình biên dịch Java đơn giản là không đủ thông minh để hiểu đúng kịch bản của bạn ở đó. Tuy nhiên, bạn có thể giúp trình biên dịch bằng cách cho anh ta một gợi ý:

List<Car> cars;
List<? extends Automobile> automobiles = cars;   // no error

Chống chỉ định

Mặt trái của đồng phương sai là chống chỉ định. Trong trường hợp hiệp phương sai, các kiểu tham số phải có mối quan hệ kiểu con, ngược lại, chúng phải có mối quan hệ siêu kiểu. Điều này có thể được coi là một thừa kế giới hạn trên: bất kỳ siêu kiểu nào cũng được cho phép và bao gồm loại được chỉ định:

class AutoColorComparer implements Comparator<Automobile>
    public int compare(Automobile a, Automobile b) {
        // Return comparison of colors
    }

Điều này có thể được sử dụng với Collections.sort :

public static <T> void sort(List<T> list, Comparator<? super T> c)

// Which you can call like this, without errors:
List<Car> cars = getListFromSomewhere();
Collections.sort(cars, new AutoColorComparer());

Bạn thậm chí có thể gọi nó bằng một bộ so sánh so sánh các đối tượng và sử dụng nó với bất kỳ loại nào.

Khi nào nên sử dụng contra hoặc co-variance?

Một chút OT có lẽ, bạn đã không hỏi, nhưng nó giúp hiểu được việc trả lời câu hỏi của bạn. Nói chung, khi bạn nhận được một cái gì đó, sử dụng hiệp phương sai và khi bạn đặt một cái gì đó, sử dụng chống chỉ định. Điều này được giải thích tốt nhất trong câu trả lời cho câu hỏi Stack Overflow Làm thế nào chống chỉ định sẽ được sử dụng trong các tổng quát Java? .

Vậy thì nó là gì với List<? extends Map<String, String>>

Bạn sử dụng extends, vì vậy các quy tắc cho hiệp phương sai được áp dụng. Ở đây bạn có một danh sách các bản đồ và mỗi mục bạn lưu trữ trong danh sách phải là một Map<string, string>hoặc xuất phát từ nó. Câu lệnh List<Map<String, String>>không thể xuất phát từ Map, nhưng phải là a Map .

Do đó, những điều sau đây sẽ hoạt động, bởi vì TreeMapkế thừa từ Map:

List<Map<String, String>> mapList = new ArrayList<Map<String, String>>();
mapList.add(new TreeMap<String, String>());

nhưng điều này sẽ không:

List<? extends Map<String, String>> mapList = new ArrayList<? extends Map<String, String>>();
mapList.add(new TreeMap<String, String>());

và điều này cũng không hoạt động, bởi vì nó không thỏa mãn ràng buộc hiệp phương sai:

List<? extends Map<String, String>> mapList = new ArrayList<? extends Map<String, String>>();
mapList.add(new ArrayList<String>());   // This is NOT allowed, List does not implement Map

Còn gì nữa không

Điều này có thể rõ ràng, nhưng bạn có thể đã lưu ý rằng việc sử dụng extendstừ khóa chỉ áp dụng cho tham số đó và không áp dụng cho phần còn lại. Tức là, sau đây sẽ không biên dịch:

List<? extends Map<String, String>> mapList = new List<? extends Map<String, String>>();
mapList.add(new TreeMap<String, Element>())  // This is NOT allowed

Giả sử bạn muốn cho phép bất kỳ loại nào trong bản đồ, với một khóa là chuỗi, bạn có thể sử dụng extendtrên từng tham số loại. Tức là, giả sử bạn xử lý XML và bạn muốn lưu trữ AttrNode, Element, v.v. trên bản đồ, bạn có thể làm một cái gì đó như:

List<? extends Map<String, ? extends Node>> listOfMapsOfNodes = new...;

// Now you can do:
listOfMapsOfNodes.add(new TreeMap<Sting, Element>());
listOfMapsOfNodes.add(new TreeMap<Sting, CDATASection>());

Không có gì trong "Vậy thì nó là gì với ..." sẽ được biên dịch.
NobleUplift

@NobleUplift: thật khó để giúp bạn nếu bạn không cung cấp thông báo lỗi bạn nhận được. Cũng xem xét, như một cách thay thế, để đặt một câu hỏi mới về SO để có câu trả lời của bạn, cơ hội thành công lớn hơn. Đoạn mã trên chỉ là đoạn trích, nó phụ thuộc vào bạn đã triển khai nó trong kịch bản của mình.
Abel

Tôi không có câu hỏi mới, tôi có những cải tiến. List<? extends Map<String, String>> mapList = new ArrayList<? extends Map<String, String>>(); mapList.add(new TreeMap<String, String>());kết quả trong found: ? extends java.util.Map<java.lang.String,java.lang.String> required: class or interface without bounds. List<Map<String, String>> mapList = new ArrayList<Map<String, String>>(); mapList.add(new TreeMap<String, String>());hoạt động hoàn hảo. Ví dụ cuối cùng rõ ràng là chính xác.
NobleUplift

@NobleUplift: xin lỗi, đi du lịch thời gian dài, sẽ khắc phục khi tôi quay lại và cảm ơn vì đã chỉ ra lỗi phát sáng! :)
Abel

Không có vấn đề, tôi chỉ vui mừng vì nó sẽ được sửa chữa. Tôi đã thực hiện các chỉnh sửa cho bạn nếu bạn muốn phê duyệt chúng.
NobleUplift

4

Hôm nay, tôi đã sử dụng tính năng này, vì vậy đây là ví dụ thực tế rất mới mẻ của tôi. (Tôi đã thay đổi tên lớp và tên phương thức thành tên chung để chúng không bị phân tâm khỏi điểm thực tế.)

Tôi có một phương pháp mà có nghĩa là phải chấp nhận một Setsố Ađối tượng mà tôi ban đầu được viết với chữ ký này:

void myMethod(Set<A> set)

Nhưng nó thực sự muốn gọi nó với Sets các lớp con của A. Nhưng điều này không được phép! (Lý do cho điều đó là, myMethodcó thể thêm các đối tượng setthuộc loại đó A, nhưng không phải là kiểu conset đối tượng 's được tuyên bố là tại địa điểm của người gọi. Vì vậy, điều này có thể phá vỡ hệ thống kiểu nếu có thể được.)

Bây giờ đến đây để giải cứu, bởi vì nó hoạt động như dự định nếu tôi sử dụng chữ ký phương thức này thay thế:

<T extends A> void myMethod(Set<T> set)

hoặc ngắn hơn, nếu bạn không cần sử dụng loại thực tế trong thân phương thức:

void myMethod(Set<? extends A> set)

Theo cách này, setkiểu của nó trở thành một tập hợp các đối tượng của kiểu con thực tế A, do đó có thể sử dụng kiểu này với các lớp con mà không gây nguy hiểm cho hệ thống kiểu.


0

Như bạn đã đề cập, có thể có hai phiên bản dưới đây để xác định Danh sách:

  1. List<? extends Map<String, String>>
  2. List<?>

2 rất cởi mở. Nó có thể giữ bất kỳ loại đối tượng. Điều này có thể không hữu ích trong trường hợp bạn muốn có một bản đồ của một loại nhất định. Trong trường hợp ai đó vô tình đặt một loại bản đồ khác, ví dụ,Map<String, int> . Phương pháp tiêu dùng của bạn có thể phá vỡ.

Để đảm bảo rằng Listcó thể chứa các đối tượng của một kiểu nhất định, các tổng quát Java đã giới thiệu ? extends. Vì vậy, trong # 1, Listcó thể giữ bất kỳ đối tượng nào có nguồn gốc từ Map<String, String>loại. Thêm bất kỳ loại dữ liệu khác sẽ ném một ngoại lệ.

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.