Tại sao Bản đồ Java không mở rộng Bộ sưu tập?


146

Tôi đã ngạc nhiên bởi thực tế đó Map<?,?>không phải là một Collection<?>.

Tôi nghĩ rằng nó sẽ có ý nghĩa rất nhiều nếu nó được tuyên bố như vậy:

public interface Map<K,V> extends Collection<Map.Entry<K,V>>

Rốt cuộc, một Map<K,V>bộ sưu tập Map.Entry<K,V>là gì, phải không?

Vì vậy, có một lý do tốt tại sao nó không được thực hiện như vậy?


Cảm ơn Cletus về câu trả lời có thẩm quyền nhất, nhưng tôi vẫn tự hỏi tại sao, nếu bạn đã có thể xem Map<K,V>dưới dạng Set<Map.Entries<K,V>>(thông qua entrySet()), thay vào đó, nó không chỉ mở rộng giao diện đó.

Nếu a Maplà a Collection, các yếu tố là gì? Câu trả lời hợp lý duy nhất là "Cặp giá trị khóa"

Chính xác, interface Map<K,V> extends Set<Map.Entry<K,V>>sẽ rất tuyệt!

nhưng điều này cung cấp một sự Maptrừu tượng hóa rất hạn chế (và không đặc biệt hữu ích) .

Nhưng nếu đó là trường hợp thì tại sao được entrySetchỉ định bởi giao diện? Nó phải hữu ích bằng cách nào đó (và tôi nghĩ thật dễ dàng để tranh luận cho vị trí đó!).

Bạn không thể hỏi giá trị của một bản đồ khóa cụ thể là gì, bạn cũng không thể xóa mục nhập cho một khóa đã cho mà không biết giá trị của bản đồ đó là gì.

Tôi không nói rằng đó là tất cả để làm điều đó Map! Nó có thể và nên giữ tất cả các phương thức khác (ngoại trừ entrySet, hiện đang dư thừa)!


1
sự kiện Đặt <Map.Entry <K, V >> thực sự
Maurice Perry

3
Nó chỉ không. Nó dựa trên một ý kiến ​​(như được mô tả trong Câu hỏi thường gặp về thiết kế), chứ không phải là một kết luận hợp lý. Để có cách tiếp cận khác, hãy xem thiết kế của các thùng chứa trong C ++ STL ( sgi.com/tech/stl/table_of_contents.html ) dựa trên phân tích kỹ lưỡng và tuyệt vời của Stepanov.
beldaz

Câu trả lời:


123

Từ Câu hỏi thường gặp về Thiết kế API của Bộ sưu tập Java :

Tại sao Bản đồ không mở rộng Bộ sưu tập?

Đây là do thiết kế. Chúng tôi cảm thấy rằng ánh xạ không phải là bộ sưu tập và bộ sưu tập không phải là ánh xạ. Do đó, rất ít ý nghĩa khi Map mở rộng giao diện Bộ sưu tập (hoặc ngược lại).

Nếu Bản đồ là Bộ sưu tập, các yếu tố là gì? Câu trả lời hợp lý duy nhất là "Các cặp giá trị khóa", nhưng điều này cung cấp sự trừu tượng hóa Bản đồ rất hạn chế (và không đặc biệt hữu ích). Bạn không thể hỏi giá trị của một bản đồ khóa cụ thể là gì, bạn cũng không thể xóa mục nhập cho một khóa đã cho mà không biết giá trị của bản đồ đó là gì.

Bộ sưu tập có thể được thực hiện để mở rộng Bản đồ, nhưng điều này đặt ra câu hỏi: các phím là gì? Không có câu trả lời thực sự thỏa đáng, và buộc một người dẫn đến một giao diện không tự nhiên.

Bản đồ có thể được xem dưới dạng Bộ sưu tập (của khóa, giá trị hoặc cặp) và thực tế này được phản ánh trong ba "Hoạt động xem bộ sưu tập" trên Bản đồ (keyset, entryset và giá trị). Về nguyên tắc, về nguyên tắc, có thể xem Danh sách dưới dạng chỉ mục ánh xạ bản đồ cho các phần tử, điều này có thuộc tính khó chịu khi xóa một phần tử khỏi Danh sách sẽ thay đổi Khóa được liên kết với mọi phần tử trước phần tử bị xóa. Đó là lý do tại sao chúng tôi không có thao tác xem bản đồ trên Danh sách.

Cập nhật: Tôi nghĩ rằng trích dẫn trả lời hầu hết các câu hỏi. Thật đáng để nhấn mạnh phần về một bộ sưu tập các mục không phải là một sự trừu tượng đặc biệt hữu ích. Ví dụ:

Set<Map.Entry<String,String>>

sẽ cho phép:

set.add(entry("hello", "world"));
set.add(entry("hello", "world 2");

(giả sử một entry() phương thức tạo ra mộtMap.Entry thể hiện)

Maps yêu cầu các khóa duy nhất để điều này vi phạm điều này. Hoặc nếu bạn áp đặt các khóa duy nhất cho một Setmục, thì đó không thực sự là mộtSet ý nghĩa chung. nó là mộtSet hạn chế hơn nữa.

Có thể cho rằng bạn có thể nói equals()/ hashCode()mối quan hệ choMap.Entry hoàn toàn nằm trên chìa khóa nhưng ngay cả điều đó cũng có vấn đề. Quan trọng hơn, nó thực sự thêm bất kỳ giá trị? Bạn có thể thấy sự trừu tượng này bị phá vỡ khi bạn bắt đầu nhìn vào các trường hợp góc.

Điều đáng chú ý HashSetlà thực tế được thực hiện như mộtHashMap , không phải là cách khác. Đây hoàn toàn là một chi tiết thực hiện nhưng dù sao cũng thú vị.

Lý do chính entrySet()để tồn tại là để đơn giản hóa việc truyền tải để bạn không phải duyệt qua các phím và sau đó thực hiện tra cứu khóa. Đừng coi đó là bằng chứng prima facie rằng một mục Mapnên là một Setmục (imho).


1
Bản cập nhật rất thuyết phục, nhưng thực sự, chế độ xem Set được trả về bởi entrySet()không hỗ trợ remove, trong khi nó không hỗ trợ add(có thể ném UnsupportedException). Vì vậy, tôi thấy quan điểm của bạn, nhưng sau đó, một lần nữa, tôi cũng thấy quan điểm của OP .. (Ý kiến ​​của riêng tôi được nêu trong câu trả lời của riêng tôi ..)
Enno Shioji

1
Zwei mang đến một điểm tốt. Tất cả những sự phức tạp do addcó thể được xử lý giống như cách entrySet()xem: cho phép một số thao tác và không cho phép các thao tác khác. Tất nhiên, một phản ứng tự nhiên là "Loại Setnào không hỗ trợ add?" - tốt, nếu hành vi đó được chấp nhận cho entrySet(), thì tại sao nó không được chấp nhận this? Điều đó nói rằng, tôi đang chủ yếu là thuyết phục đã lý do tại sao đây không phải là một ý tưởng tuyệt vời như Tôi đã từng suy nghĩ, nhưng tôi vẫn nghĩ đó là xứng đáng với cuộc tranh luận hơn nữa, nếu chỉ làm phong phú thêm sự hiểu biết của tôi về những gì làm cho một thiết kế tốt API / OOP.
polygenelubricants

3
Đoạn đầu tiên của trích dẫn từ Câu hỏi thường gặp là buồn cười: "Chúng tôi cảm thấy ... do đó ... có ý nghĩa rất nhỏ". Tôi không cảm thấy rằng "cảm giác" và "cảm giác" tạo thành một kết luận; o)
Peter Wippermann

Bộ sưu tập có thể được thực hiện để mở rộng Bản đồ ? Không chắc chắn, tại sao tác giả nghĩ cho Collectionmở rộng Map?
trao đổi quá mức

11

Mặc dù bạn đã nhận được một số câu trả lời bao gồm câu hỏi của bạn khá trực tiếp, tôi nghĩ rằng có thể hữu ích để lùi lại một chút và nhìn vào câu hỏi nói chung hơn một chút. Đó là, không nhìn vào cách cụ thể thư viện Java được viết và xem tại sao nó được viết theo cách đó.

Vấn đề ở đây là sự kế thừa chỉ mô hình một loại phổ biến. Nếu bạn chọn ra hai thứ mà cả hai đều giống như "bộ sưu tập", bạn có thể chọn ra 8 hoặc 10 thứ chúng có chung. Nếu bạn chọn ra một cặp "giống như bộ sưu tập" khác, chúng cũng sẽ có 8 hoặc 10 điểm chung - nhưng chúng sẽ không giống 8 hoặc 10 thứ như cặp đầu tiên.

Nếu bạn nhìn vào một chục hay như vậy khác nhau "bộ sưu tập giống như" sự vật, hầu như mỗi một trong số họ sẽ có thể có một cái gì đó giống như 8 hoặc 10 đặc điểm chung với ít nhất một ai khác - nhưng nếu bạn nhìn vào những gì được chia sẻ trên mỗi một trong số họ, thực tế bạn không còn gì cả.

Đây là một tình huống mà thừa kế (đặc biệt là thừa kế đơn) không thể mô hình tốt. Không có ranh giới rõ ràng giữa cái nào trong số chúng thực sự là bộ sưu tập và cái nào không - nhưng nếu bạn muốn xác định một lớp Bộ sưu tập có ý nghĩa, bạn sẽ bị mắc kẹt với việc loại bỏ một vài trong số chúng. Nếu bạn chỉ để lại một vài trong số chúng, lớp Collection của bạn sẽ chỉ có thể cung cấp giao diện khá thưa thớt. Nếu bạn để lại nhiều hơn, bạn sẽ có thể cung cấp cho nó một giao diện phong phú hơn.

Một số người cũng sử dụng tùy chọn cơ bản để nói: "loại bộ sưu tập này hỗ trợ thao tác X, nhưng bạn không được phép sử dụng nó, bằng cách xuất phát từ một lớp cơ sở xác định X, nhưng cố gắng sử dụng lớp dẫn xuất 'X thất bại (ví dụ: , bằng cách ném một ngoại lệ).

Điều đó vẫn để lại một vấn đề: hầu như bất kể bạn bỏ đi và bỏ vào đâu, bạn sẽ phải vạch ra một ranh giới khó khăn giữa những gì các lớp học và những gì được đưa ra ngoài. Bất kể bạn vẽ đường đó ở đâu, bạn sẽ bị bỏ lại với sự phân chia rõ ràng, khá giả tạo giữa một số thứ khá giống nhau.


10

Tôi đoán tại sao là chủ quan.

Trong C #, tôi nghĩ Dictionarymở rộng hoặc ít nhất là thực hiện một bộ sưu tập:

public class Dictionary<TKey, TValue> : IDictionary<TKey, TValue>, 
    ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>, 
    IDictionary, ICollection, IEnumerable, ISerializable, IDeserializationCallback

Trong Pharo Smalltak cũng vậy:

Collection subclass: #Set
Set subclass: #Dictionary

Nhưng có một sự bất cân xứng với một số phương pháp. Chẳng hạn, collect:sẽ lấy liên kết (tương đương với một mục), trong khi do:lấy các giá trị. Họ cung cấp một phương pháp khác keysAndValuesDo:để lặp lại từ điển bằng cách nhập. Add:mất một hiệp hội, nhưng remove:đã bị "đàn áp":

remove: anObject
self shouldNotImplement 

Vì vậy, nó chắc chắn có thể thực hiện được, nhưng dẫn đến một số vấn đề khác liên quan đến hệ thống phân cấp lớp.

Những gì tốt hơn là chủ quan.


3

Câu trả lời của cletus là tốt, nhưng tôi muốn thêm một cách tiếp cận ngữ nghĩa. Để kết hợp cả hai vô nghĩa, hãy nghĩ đến trường hợp bạn thêm một cặp khóa-giá trị thông qua giao diện bộ sưu tập và khóa đã tồn tại. Giao diện Bản đồ chỉ cho phép một giá trị được liên kết với khóa. Nhưng nếu bạn tự động xóa mục nhập hiện có bằng cùng một khóa, bộ sưu tập có sau khi thêm cùng kích thước như trước - rất bất ngờ cho một bộ sưu tập.


4
Đây là cách Setcác công trình, và Set không thực hiện Collection.
Lii

2

Bộ sưu tập Java bị hỏng. Có một giao diện còn thiếu, đó là Relation. Do đó, Map mở rộng Relation extends Set. Quan hệ (còn được gọi là đa bản đồ) có các cặp tên-giá trị duy nhất. Bản đồ (còn gọi là "Chức năng"), có các tên (hoặc khóa) duy nhất ánh xạ tới các giá trị. Chuỗi mở rộng Bản đồ (trong đó mỗi khóa là một số nguyên> 0). Túi (hoặc nhiều bộ) mở rộng Bản đồ (trong đó mỗi khóa là một phần tử và mỗi giá trị là số lần phần tử xuất hiện trong túi).

Cấu trúc này sẽ cho phép giao nhau, kết hợp, vv của một loạt các "bộ sưu tập". Do đó, hệ thống phân cấp phải là:

                                Set

                                 |

                              Relation

                                 |

                                Map

                                / \

                             Bag Sequence

Sun / Oracle / Java ppl - vui lòng lấy lại ngay lần sau. Cảm ơn.


4
Tôi muốn xem API cho các giao diện tưởng tượng này chi tiết. Không chắc chắn tôi muốn một Chuỗi (còn gọi là Danh sách) trong đó khóa là Số nguyên; Nghe có vẻ như một hiệu suất lớn.
Lawrence Dol

Đúng vậy, một chuỗi là một danh sách - do đó Chuỗi mở rộng Danh sách. Tôi không biết liệu bạn có thể truy cập cái này không, nhưng nó có thể là cổng thông tin
xagyg

@SoftwareMonkey ở đây là một liên kết khác. Liên kết cuối cùng là liên kết đến javadoc của API. zedlib.sourceforge.net
xagyg

@SoftwareMonkey Tôi vô tình thừa nhận rằng thiết kế là mối quan tâm chính của tôi ở đây. Tôi giả định rằng các bậc thầy tại Oracle / Sun có thể tối ưu hóa cái quái gì đó.
xagyg

Tôi có xu hướng không đồng ý. Làm sao có thể giữ "Bản đồ là một tập hợp" nếu bạn luôn không thể thêm các thành phần không phải là thành viên của tên miền vào tập hợp của mình (như trong ví dụ trong câu trả lời của @cletus).
einpoklum

1

Nếu bạn nhìn vào cấu trúc dữ liệu tương ứng, bạn có thể dễ dàng đoán tại sao Mapnó không phải là một phần của Collection. Mỗi Collectioncửa hàng lưu trữ một giá trị trong đó như một Mapcặp lưu trữ khóa-giá trị. Vì vậy, các phương thức trong Collectiongiao diện không tương thích với Mapgiao diện. Ví dụ trong Collectionchúng ta có add(Object o). Điều gì sẽ được thực hiện như vậy trong Map. Không có nghĩa gì khi có một phương pháp như vậy Map. Thay vào đó chúng tôi có một put(key,value)phương pháp trong Map.

Cùng tranh luận đi cho addAll(), remove()removeAll()phương pháp. Vì vậy, lý do chính là sự khác biệt trong cách lưu trữ dữ liệu MapCollection. Ngoài ra, nếu bạn nhớ lại Collectiongiao diện đã thực hiện Iterablegiao diện, tức là bất kỳ giao diện nào có .iterator()phương thức sẽ trả về một trình vòng lặp phải cho phép chúng ta lặp lại các giá trị được lưu trữ trong Collection. Bây giờ những gì phương pháp như vậy sẽ trở lại cho một Map? Trình lặp chính hoặc Trình lặp giá trị? Điều này cũng không có ý nghĩa.

Có nhiều cách để chúng ta có thể lặp lại các khóa và giá trị lưu trữ trong một Mapvà đó là cách nó là một phần của Collectionkhung.


There are ways in which we can iterate over keys and values stores in a Map and that is how it is a part of Collection framework.Bạn có thể hiển thị một ví dụ mã cho điều này?
RamenChef

Điều đó đã được giải thích rất tốt. Bây giờ tôi hiểu rồi. Cảm ơn!
Dubai Memic

Việc thực hiện add(Object o)sẽ là thêm một Entry<ClassA, ClassB>đối tượng. A Mapcó thể được coi là mộtCollection of Tuples
LIvanov

0

Chính xác, interface Map<K,V> extends Set<Map.Entry<K,V>>sẽ rất tuyệt!

Trên thực tế, nếu có implements Map<K,V>, Set<Map.Entry<K,V>>, thì tôi có xu hướng đồng ý .. Nó có vẻ tự nhiên. Nhưng điều đó không làm việc rất tốt, phải không? Giả sử chúng ta có HashMap implements Map<K,V>, Set<Map.Entry<K,V>, LinkedHashMap implements Map<K,V>, Set<Map.Entry<K,V>v.v ... điều đó tốt, nhưng nếu bạn cóentrySet() , sẽ không ai quên thực hiện phương pháp đó và bạn có thể chắc chắn rằng bạn có thể nhận được mục Set cho bất kỳ Bản đồ nào, trong khi bạn không hy vọng rằng người triển khai đã thực hiện cả hai giao diện ...

Lý do tôi không muốn có interface Map<K,V> extends Set<Map.Entry<K,V>>chỉ đơn giản là vì sẽ có nhiều phương pháp hơn. Và sau tất cả, chúng là những thứ khác nhau, phải không? Cũng rất thực tế, nếu tôi nhấn map.vào IDE, tôi không muốn xem .remove(Object obj), và .remove(Map.Entry<K,V> entry)vì tôi không thể làm hit ctrl+space, r, returnvà được thực hiện với nó.


Dường như với tôi rằng, nếu người ta có thể tuyên bố, về mặt ngữ nghĩa, rằng "Bản đồ là - Thiết lập trên Bản đồ.Entry's" thì người ta sẽ bận tâm thực hiện phương pháp liên quan; và nếu người ta không thể đưa ra tuyên bố đó, thì người ta sẽ không - tức là nó sẽ gây rắc rối.
einpoklum

0

Map<K,V>không nên gia hạn Set<Map.Entry<K,V>>kể từ:

  • Bạn không thể thêm các Map.Entrys khác nhau với cùng một khóa Map, nhưng
  • Bạn có thể thêm các Map.Entrys khác nhau với cùng một khóa Set<Map.Entry>.

... đây là một tuyên bố cô đọng về sự ghen tị của phần thứ hai trong câu trả lời của @cletus.
einpoklum

-2

Thẳng và đơn giản. Bộ sưu tập là một giao diện chỉ mong đợi một Đối tượng, trong khi Bản đồ yêu cầu Hai.

Collection(Object o);
Map<Object,Object>

Trinad đẹp, câu trả lời của bạn luôn đơn giản và ngắn gọn.
Sairam
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.