Por que o Java Map não estende a coleção?

146

Fiquei surpreso com o fato de que Map<?,?>não é um Collection<?>.

Eu pensei que faria muito sentido se fosse declarado como tal:

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

Afinal, a Map<K,V>é uma coleção de Map.Entry<K,V>, não é?

Então, há uma boa razão para que não seja implementado como tal?


Agradeço a Cletus pela resposta mais autoritária, mas ainda estou me perguntando por que, se você já pode ver um Map<K,V>como Set<Map.Entries<K,V>>(via entrySet()), ele não apenas estende essa interface.

Se a Mapé a Collection, quais são os elementos? A única resposta razoável é "pares de valores-chave"

Exatamente, interface Map<K,V> extends Set<Map.Entry<K,V>>seria ótimo!

mas isso fornece uma Mapabstração muito limitada (e não particularmente útil) .

Mas se for esse o caso, por que é entrySetespecificado pela interface? Deve ser útil de alguma forma (e acho que é fácil argumentar sobre essa posição!).

Você não pode perguntar para qual valor uma determinada chave é mapeada nem excluir a entrada de uma determinada chave sem saber para qual valor ela é mapeada.

Não estou dizendo que é só isso Map! Pode e deve manter todos os outros métodos (exceto entrySetagora redundantes)!

poligenelubricants
fonte
1
evento Set <Map.Entry <K, V >> na verdade
Maurice Perry
3
Simplesmente não. É baseado em uma opinião (conforme descrito nas Perguntas frequentes sobre design), em vez de ser uma conclusão lógica. Para uma abordagem alternativa, observe o design de contêineres no C ++ STL ( sgi.com/tech/stl/table_of_contents.html ), baseado na análise completa e brilhante de Stepanov.
beldaz

Respostas:

123

Nas Perguntas frequentes sobre o design da API Java Collections :

Por que o Map não estende a coleção?

Isso foi por design. Achamos que os mapeamentos não são coleções e coleções não são mapeamentos. Portanto, faz pouco sentido para o Map estender a interface Collection (ou vice-versa).

Se um mapa é uma coleção, quais são os elementos? A única resposta razoável é "Pares de valor-chave", mas isso fornece uma abstração de mapa muito limitada (e não particularmente útil). Você não pode perguntar para qual valor uma determinada chave é mapeada nem excluir a entrada de uma determinada chave sem saber para qual valor ela é mapeada.

Pode-se fazer uma coleta para estender o Mapa, mas isso levanta a questão: quais são as chaves? Não existe uma resposta realmente satisfatória, e forçar um leva a uma interface não natural.

Os mapas podem ser visualizados como coleções (de chaves, valores ou pares), e esse fato é refletido nas três "operações de exibição de coleção" nos mapas (keySet, entrySet e valores). Embora seja, em princípio, possível visualizar uma Lista como um mapa, mapeando índices para elementos, isso tem a propriedade desagradável de que excluir um elemento da Lista altera a Chave associada a todos os elementos antes do elemento excluído. É por isso que não temos uma operação de visualização de mapa nas Listas.

Atualização: acho que a citação responde à maioria das perguntas. Vale ressaltar que a parte de uma coleção de entradas não é uma abstração particularmente útil. Por exemplo:

Set<Map.Entry<String,String>>

permitiria:

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

(assumindo um entry()método que cria uma Map.Entryinstância)

Maps exigem chaves exclusivas, portanto isso violaria isso. Ou se você impõe chaves exclusivas em uma Setdas entradas, não é realmente uma Setno sentido geral. É um Setcom mais restrições.

Indiscutivelmente, você poderia dizer que o relacionamento equals()/ estava puramente essencial, mas mesmo isso tem problemas. Mais importante, isso realmente agrega algum valor? Você pode achar que essa abstração é interrompida quando você começa a examinar os casos de canto.hashCode()Map.Entry

Vale a pena notar que o HashSeté realmente implementado como um HashMap, e não o contrário. Isso é puramente um detalhe de implementação, mas é interessante, no entanto.

A principal razão de entrySet()existir é simplificar a travessia para que você não precise atravessar as teclas e, em seguida, faça uma busca na chave. Não tome isso como evidência prima facie de que a Mapdeve ser uma Setdas entradas (imho).

cleto
fonte
1
A atualização é muito convincente, mas, de fato, a exibição de conjunto retornada por entrySet()suporta remove, enquanto não suporta add(provavelmente lança UnsupportedException). Então eu vejo o seu ponto, mas, novamente, não vejo o ponto do OP, também .. (Minha opinião é como indicado na minha própria resposta ..)
Enno Shioji
1
Zwei traz um bom argumento. Todas essas complicações decorrentes addpodem ser tratadas da mesma maneira que a entrySet()exibição: permite algumas operações e outras não. Uma resposta natural, é claro, é "Que tipo de Setsuporte não é suportado add?" - bem, se esse comportamento é aceitável entrySet(), por que não é aceitável this? Dito isto, já estou bastante convencido de por que essa não é uma idéia tão boa quanto eu pensava, mas ainda acho que vale a pena um debate adicional, apenas para enriquecer minha própria compreensão do que faz um bom design de API / OOP.
polygenelubricants
3
O primeiro parágrafo da citação do FAQ é engraçado: "Nós sentimos ... assim ... faz pouco sentido". Eu não sinto que "sensação" e "sentido" formar uma conclusão; o)
Peter Wippermann
A coleta pode ser feita para estender o Mapa ? Não tenho certeza, por que o autor pensa em Collectionestender Map?
overexchange
11

Embora você tenha recebido várias respostas que abordam sua pergunta de maneira bastante direta, acho que pode ser útil recuar um pouco e analisar a questão de maneira mais geral. Ou seja, para não olhar especificamente como a biblioteca Java é escrita, e por que ela é escrita dessa maneira.

O problema aqui é que a herança apenas modela um tipo de semelhança. Se você escolher duas coisas que pareçam "parecidas com coleções", provavelmente poderá escolher 8 ou 10 coisas que elas têm em comum. Se você escolher um par diferente de itens "semelhantes a coleções", eles também terão 8 ou 10 itens em comum - mas não serão os mesmos 8 ou 10 itens do primeiro par.

Se você observar uma dúzia de coisas "semelhantes a coleções", virtualmente todas elas provavelmente terão algo em torno de 8 ou 10 características em comum com pelo menos uma outra - mas se você observar o que é compartilhado em todos os um deles, você fica com praticamente nada.

Essa é uma situação em que a herança (especialmente a herança única) simplesmente não é um modelo adequado. Não existe uma linha divisória limpa entre quais são realmente coleções e quais não são - mas se você deseja definir uma classe Collection significativa, você fica impedido de deixar algumas delas de fora. Se você deixar apenas alguns deles de fora, sua classe Collection poderá fornecer apenas uma interface bastante esparsa. Se você deixar mais de fora, poderá oferecer uma interface mais rica.

Alguns também adotam a opção de dizer basicamente: "esse tipo de coleção suporta a operação X, mas você não pode usá-la, derivando de uma classe base que define X, mas tentando usar a classe derivada 'X falha (por exemplo, , lançando uma exceção).

Isso ainda deixa um problema: quase independentemente de qual você deixa de fora e de quem coloca, você terá que traçar uma linha dura entre quais classes estão e quais estão fora. Não importa onde você traça essa linha, você ficará com uma divisão clara, bastante artificial, entre algumas coisas que são bastante semelhantes.

Jerry Coffin
fonte
10

Eu acho que o porquê é subjetivo.

Em C #, acho que Dictionaryestende ou pelo menos implementa uma coleção:

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

No Pharo Smalltak também:

Collection subclass: #Set
Set subclass: #Dictionary

Mas há uma assimetria com alguns métodos. Por exemplo, collect:aceita associação (o equivalente a uma entrada), enquanto do:aceita os valores. Eles fornecem outro método keysAndValuesDo:para iterar o dicionário por entrada. Add:leva uma associação, mas remove:foi "suprimido":

remove: anObject
self shouldNotImplement 

Portanto, é definitivamente factível, mas leva a outros problemas relacionados à hierarquia de classes.

O que é melhor é subjetivo.

ewernli
fonte
3

A resposta do cletus é boa, mas quero adicionar uma abordagem semântica. Para combinar os dois, não faz sentido, pense no caso de adicionar um par de valores-chave por meio da interface de coleção e a chave já existe. A interface do mapa permite apenas um valor associado à chave. Mas se você remover automaticamente a entrada existente com a mesma chave, a coleção terá o mesmo tamanho de antes de adicionar - muito inesperado para uma coleção.

Mnementh
fonte
4
É assim que Setfunciona, e Set não implementar Collection.
Lii
2

Coleções Java estão quebradas. Há uma interface ausente, a de Relation. Portanto, o mapa estende a relação estende o conjunto. As relações (também chamadas de multi-mapas) têm pares nome-valor exclusivos. Os mapas (também conhecidos como "Funções") têm nomes (ou chaves) exclusivos que, naturalmente, são mapeados para valores. Sequências estendem Mapas (onde cada tecla é um número inteiro> 0). Bolsas (ou conjuntos múltiplos) estendem Mapas (onde cada tecla é um elemento e cada valor é o número de vezes que o elemento aparece na bolsa).

Essa estrutura permitiria a interseção, união etc. de uma variedade de "coleções". Portanto, a hierarquia deve ser:

                                Set

                                 |

                              Relation

                                 |

                                Map

                                / \

                             Bag Sequence

Sun / Oracle / Java ppl - por favor, acerte da próxima vez. Obrigado.

xagyg
fonte
4
Eu gostaria de ver as APIs dessas interfaces imaginárias detalhadas. Não tenho certeza se gostaria de uma sequência (também conhecida como lista) em que a chave era um número inteiro; isso soa como um enorme sucesso de desempenho.
Lawrence Dol
É isso mesmo, uma sequência é uma lista - portanto, a Sequence estende a List. Não sei se você pode acessar isso, mas pode ser interessante portal.acm.org/citation.cfm?id=1838687.1838705
xagyg
@SoftwareMonkey aqui está outro link. Último link é o link para o javadoc da API. zedlib.sourceforge.net
xagyg
@SoftwareMonkey Confesso sem vergonha que o design é minha principal preocupação aqui. Eu estou assumindo que os gurus da Oracle / Sun poderiam otimizar o diabo.
Xagyg
Eu tendo a discordar. Como ele pode sustentar que "O mapa é um conjunto" se você nem sempre pode adicionar elementos não membros do domínio ao seu conjunto (como no exemplo da resposta do @cletus ').
einpoklum
1

Se você olhar para a respectiva estrutura de dados, poderá adivinhar facilmente por que Mapnão faz parte Collection. Cada Collectionum armazena um único valor em que, como um Mappar de valores-chave, armazena. Portanto, os métodos na Collectioninterface são incompatíveis para a Mapinterface. Por exemplo, Collectionnós temos add(Object o). Em que seria essa implementação Map? Não faz sentido ter esse método Map. Em vez disso, temos um put(key,value)método emMap .

Mesmo argumento vale para addAll(), remove()e removeAll()métodos. Portanto, o principal motivo é a diferença na maneira como os dados são armazenados Mape Collection. Além disso, se você se lembrar da Collectioninterface implementada pela Iterableinterface, ou seja, qualquer interface com o .iterator()método deve retornar um iterador que deve permitir a iteração sobre os valores armazenados no Collection. Agora, o que esse método retornaria para um Map? Iterador de chaves ou um iterador de valor? Isso também não faz sentido.

Existem maneiras pelas quais podemos iterar sobre armazenamentos de chaves e valores em um Mape é assim que faz parte da Collectionestrutura.

Mayur Ingle
fonte
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.Você pode mostrar um exemplo de código para isso?
RamenChef 4/10
Isso foi muito bem explicado. Agora eu entendi. Obrigado!
Emir Memic 20/05/19
Uma implementação de add(Object o)seria adicionar um Entry<ClassA, ClassB>objeto. A Mappode ser considerado umCollection of Tuples
LIvanov
0

Exatamente, interface Map<K,V> extends Set<Map.Entry<K,V>>seria ótimo!

Na verdade, se fosse implements Map<K,V>, Set<Map.Entry<K,V>>, então eu tendem a concordar .. Parece até natural. Mas isso não funciona muito bem, certo? Digamos que temos HashMap implements Map<K,V>, Set<Map.Entry<K,V>, LinkedHashMap implements Map<K,V>, Set<Map.Entry<K,V>etc ... tudo bem, mas se você tivesseentrySet() , ninguém esquecerá de implementar esse método, e você pode ter certeza de que pode obter entrySet para qualquer mapa, enquanto não está se estiver esperando que o implementador implementou as duas interfaces ...

A razão que eu não quero ter interface Map<K,V> extends Set<Map.Entry<K,V>>é simplesmente, porque haverá mais métodos. E afinal, são coisas diferentes, certo? Também, na prática, se eu clicar map.no IDE, não quero ver .remove(Object obj), e .remove(Map.Entry<K,V> entry)porque não posso fazer hit ctrl+space, r, returne acabar com ele.

Enno Shioji
fonte
Parece-me que se alguém pudesse reivindicar, semanticamente, que "Um mapa é um conjunto no mapa.Entrada", então se incomodaria em implementar o método relevante; e se alguém não pode fazer essa afirmação, não o faria - isto é, deveria ser sobre o incômodo.
einpoklum
0

Map<K,V>não deve se estender, Set<Map.Entry<K,V>>pois:

  • Você não pode adicionar diferentes Map.Entrys com a mesma chave à mesmaMap , mas
  • Você pode adicionar diferentes Map.Entrys com a mesma chave à mesma Set<Map.Entry>.
einpoklum
fonte
... esta é uma declaração sucinta do historiador da segunda parte da resposta do @cletus.
einpoklum
-2

Direto e simples. Collection é uma interface que espera apenas um Objeto, enquanto o Mapa requer Dois.

Collection(Object o);
Map<Object,Object>
Trinadh Koya
fonte
Nice Trinad, suas respostas sempre simples e curtas.
Sairam 24/03