Có sự khác biệt nào giữa
List<Map<String, String>>
và
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à ? extends
gì?
Có sự khác biệt nào giữa
List<Map<String, String>>
và
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à ? extends
gì?
Câu trả lời:
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 List
trong HashMap
s phải là một List
trong Map
s, 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 List
số HashMap
s không phải là một List
của Map
s.
HashMap
là một Map
do đa hình.
List
số HashMap
s không phải là một List
của Map
s.
List<Map<String,String>> maps = hashMaps;
và 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)
HashMap
danh sách Map
s, cả hai ví dụ của bạn đều hợp pháp, nếu tôi đọc chúng chính xác.
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.)
List<String>
không phải là một kiểu con của List<Object>
? - xem, ví dụ, stackoverflow.com/questions/3246137/ từ
? 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ó).
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à:
Nếu bạn có một lớp Automobile
, thì Car
và Truck
là 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
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.
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? .
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ì TreeMap
kế 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
Điều này có thể rõ ràng, nhưng bạn có thể đã lưu ý rằng việc sử dụng extends
từ 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 extend
trê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>());
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.
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 Set
số 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 Set
s 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à, myMethod
có thể thêm các đối tượng set
thuộ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, set
kiể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.
Như bạn đã đề cập, có thể có hai phiên bản dưới đây để xác định Danh sách:
List<? extends Map<String, String>>
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 List
có 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, List
có 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ệ.