¿Por qué Java Map no extiende la Colección?

146

Me sorprendió el hecho de que Map<?,?>no es un Collection<?>.

Pensé que tendría mucho sentido si se declaraba como tal:

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

Después de todo, a Map<K,V>es una colección de Map.Entry<K,V>, ¿no?

Entonces, ¿hay una buena razón por la cual no se implementa como tal?


Gracias a Cletus por la respuesta más autorizada, pero todavía me pregunto por qué, si ya puede ver un Map<K,V>as Set<Map.Entries<K,V>>(vía entrySet()), no solo extiende esa interfaz.

Si a Mapes a Collection, ¿cuáles son los elementos? La única respuesta razonable es "pares clave-valor"

¡Exactamente, interface Map<K,V> extends Set<Map.Entry<K,V>>sería genial!

pero esto proporciona una Mapabstracción muy limitada (y no particularmente útil) .

Pero si ese es el caso, ¿ entrySetpor qué lo especifica la interfaz? Debe ser útil de alguna manera (¡y creo que es fácil defender esa posición!).

No puede preguntar a qué valor se asigna una clave determinada, ni puede eliminar la entrada de una clave determinada sin saber a qué valor se asigna.

¡No estoy diciendo que eso es todo Map! ¡Puede y debe mantener todos los demás métodos (excepto entrySet, que ahora es redundante)!

poligenelubricantes
fuente
1
conjunto de eventos <Map.Entry <K, V >> en realidad
Maurice Perry
3
Simplemente no lo hace. Se basa en una opinión (como se describe en las Preguntas frecuentes sobre diseño), en lugar de ser una conclusión lógica. Para un enfoque alternativo, mire el diseño de contenedores en C ++ STL ( sgi.com/tech/stl/table_of_contents.html ) que se basa en el análisis exhaustivo y brillante de Stepanov.
beldaz

Respuestas:

123

De las Preguntas frecuentes sobre el diseño de la API de colecciones Java :

¿Por qué Map no extiende la Colección?

Esto fue por diseño. Creemos que las asignaciones no son colecciones y las colecciones no son asignaciones. Por lo tanto, tiene poco sentido que Map extienda la interfaz de Collection (o viceversa).

Si un mapa es una colección, ¿cuáles son los elementos? La única respuesta razonable es "pares clave-valor", pero esto proporciona una abstracción de mapa muy limitada (y no particularmente útil). No puede preguntar a qué valor se asigna una clave determinada, ni puede eliminar la entrada de una clave determinada sin saber a qué valor se asigna.

Se podría hacer una colección para extender el Mapa, pero esto plantea la pregunta: ¿cuáles son las claves? No hay una respuesta realmente satisfactoria, y forzar una lleva a una interfaz no natural.

Los mapas se pueden ver como Colecciones (de claves, valores o pares), y este hecho se refleja en las tres "Operaciones de vista de Colección" en Mapas (keySet, entrySet y valores). Si bien, en principio, es posible ver una Lista como un Mapa que asigna índices a elementos, esto tiene la desagradable propiedad de que eliminar un elemento de la Lista cambia la Clave asociada con cada elemento antes del elemento eliminado. Es por eso que no tenemos una operación de vista de mapa en Listas.

Actualización: creo que la cita responde a la mayoría de las preguntas. Vale la pena enfatizar la parte acerca de que una colección de entradas no es una abstracción particularmente útil. Por ejemplo:

Set<Map.Entry<String,String>>

permitiría:

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

(suponiendo un entry()método que crea una Map.Entryinstancia)

Maps requieren claves únicas, por lo que esto violaría esto. O si impone claves únicas en una Setde las entradas, no es realmente una Seten el sentido general. Es una Setcon más restricciones.

Podría decirse que la relación equals()/ hashCode()para Map.Entryfue puramente clave, pero incluso eso tiene problemas. Más importante aún, ¿realmente agrega algún valor? Es posible que esta abstracción se descomponga una vez que comience a mirar los casos de esquina.

Vale la pena señalar que el HashSetrealmente se implementa como un HashMap, no al revés. Esto es puramente un detalle de implementación, pero no obstante es interesante.

La razón principal para entrySet()existir es simplificar el recorrido para que no tenga que atravesar las teclas y luego hacer una búsqueda de la clave. No lo tome como evidencia prima facie de que a Mapdebería ser una Setde entradas (en mi humilde opinión).

cletus
fuente
1
La actualización es muy convincente, pero de hecho, la vista Set devuelta por entrySet()sí es compatible remove, mientras que no es compatible add(probablemente arroja UnsupportedException). Así que veo su punto, pero de nuevo, también veo el punto del OP ... (Mi propia opinión es la que se indica en mi propia respuesta ...)
Enno Shioji
1
Zwei saca un buen punto. Todas esas complicaciones debidas addse pueden manejar de la misma manera que lo hace la entrySet()vista: permite algunas operaciones y no permite otras. Una respuesta natural, por supuesto, es "¿Qué tipo de Setno es compatible add?" - Bueno, si tal comportamiento es aceptable entrySet(), ¿por qué no lo es this? Dicho esto, ya estoy convencido de por qué esta no es una idea tan buena como alguna vez pensé, pero sigo pensando que merece más debate, aunque solo sea para enriquecer mi propia comprensión de lo que hace un buen diseño API / OOP.
polygenelubricants
3
El primer párrafo de la cita de las preguntas frecuentes es divertido: "Sentimos ... por lo tanto ... tiene poco sentido". No siento que "sentir" y "sentido" formen una conclusión; o)
Peter Wippermann
¿Se podría hacer una colección para ampliar el Mapa ? No estoy seguro, ¿por qué el autor piensa para Collectionextender Map?
intercambio excesivo el
11

Si bien ha recibido varias respuestas que cubren su pregunta de manera bastante directa, creo que podría ser útil retroceder un poco y analizar la pregunta de manera más general. Es decir, no mirar específicamente cómo se escribe la biblioteca Java y ver por qué se escribe de esa manera.

El problema aquí es que la herencia solo modela un tipo de comunidad. Si elige dos cosas que parecen "de colección", probablemente pueda elegir entre 8 o 10 cosas que tienen en común. Si elige un par diferente de cosas "de colección", también tendrán 8 o 10 cosas en común, pero no serán las mismas 8 o 10 cosas que el primer par.

Si nos fijamos en una docena de diferentes "colección como" cosas, prácticamente cada una de ellas tendrá probablemente algo así como 8 o 10 características en común con al menos otro - pero si nos fijamos en lo que se comparte a través de cada uno de ellos, te quedas prácticamente sin nada.

Esta es una situación en la que la herencia (especialmente la herencia única) simplemente no se modela bien. No hay una línea divisoria clara entre cuáles de ellas son realmente colecciones y cuáles no, pero si desea definir una clase de Colección significativa, no podrá dejar algunas. Si deja solo unos pocos, su clase de Colección solo podrá proporcionar una interfaz bastante escasa. Si deja más, podrá darle una interfaz más rica.

Algunos también toman la opción de decir básicamente: "este tipo de colección admite la operación X, pero no se le permite usarla, derivando de una clase base que define X, pero el intento de usar la clase derivada 'X falla (p. Ej. , lanzando una excepción).

Eso todavía deja un problema: casi independientemente de cuál deje fuera y cuál ingrese, tendrá que trazar una línea dura entre las clases que están dentro y las que están fuera. No importa dónde dibuje esa línea, se quedará con una división clara, más bien artificial, entre algunas cosas que son bastante similares.

Jerry Coffin
fuente
10

Supongo que el por qué es subjetivo.

En C #, creo que Dictionaryextiende o al menos implementa una colección:

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

En Pharo Smalltak también:

Collection subclass: #Set
Set subclass: #Dictionary

Pero hay una asimetría con algunos métodos. Por ejemplo, collect:will toma asociación (el equivalente de una entrada), mientras do:toma los valores. Proporcionan otro método keysAndValuesDo:para iterar el diccionario por entrada. Add:toma una asociación, pero remove:ha sido "suprimida":

remove: anObject
self shouldNotImplement 

Por lo tanto, es definitivamente factible, pero conduce a otros problemas relacionados con la jerarquía de clases.

Lo que es mejor es subjetivo.

ewernli
fuente
3

La respuesta de cletus es buena, pero quiero agregar un enfoque semántico. Para combinar ambos no tiene sentido, piense en el caso de que agregue un par clave-valor a través de la interfaz de recopilación y la clave ya existe. La interfaz de mapa solo permite un valor asociado con la clave. Pero si elimina automáticamente la entrada existente con la misma clave, la colección tiene después del agregado el mismo tamaño que antes, muy inesperado para una colección.

Mnementh
fuente
44
Así es como Setfunciona y Set se implementa Collection.
Lii
2

Las colecciones de Java están rotas. Falta una interfaz, la de Relación. Por lo tanto, el mapa extiende la relación extiende el conjunto. Las relaciones (también llamadas mapas múltiples) tienen pares de nombre-valor únicos. Los mapas (también conocidos como "Funciones") tienen nombres únicos (o claves) que, por supuesto, se asignan a valores. Las secuencias extienden Mapas (donde cada clave es un entero> 0). Las bolsas (o conjuntos múltiples) extienden Mapas (donde cada clave es un elemento y cada valor es la cantidad de veces que el elemento aparece en la bolsa).

Esta estructura permitiría la intersección, unión, etc. de una gama de "colecciones". Por lo tanto, la jerarquía debería ser:

                                Set

                                 |

                              Relation

                                 |

                                Map

                                / \

                             Bag Sequence

Sun / Oracle / Java ppl: hazlo bien la próxima vez. Gracias.

xagyg
fuente
44
Me gustaría ver las API de estas interfaces imaginarias detalladas. No estoy seguro de que me gustaría una secuencia (también conocida como Lista) donde la clave era un número entero; eso suena como un gran éxito de rendimiento.
Lawrence Dol
Así es, una secuencia es una lista, por lo tanto, Sequence extend List. No sé si puede acceder a esto, pero puede ser interesante portal.acm.org/citation.cfm?id=1838687.1838705
xagyg
@SoftwareMonkey aquí hay otro enlace. El último enlace es el enlace al javadoc de la API. zedlib.sourceforge.net
xagyg
@SoftwareMonkey Admito descaradamente que el diseño es mi principal preocupación aquí. Supongo que los gurús de Oracle / Sun podrían optimizarlo.
xagyg
Tiendo a estar en desacuerdo. ¿Cómo puede contener ese "Mapa es un conjunto" si no siempre puede agregar elementos del dominio que no sean miembros a su conjunto (como en el ejemplo en la respuesta de @cletus)?
einpoklum
1

Si observa la estructura de datos respectiva, puede adivinar fácilmente por qué Mapno forma parte de ella Collection. Cada uno Collectionalmacena un valor único donde como un Mappar clave-valor almacena. Por lo tanto, los métodos en la Collectioninterfaz son incompatibles para la Mapinterfaz. Por ejemplo en Collectiontenemos add(Object o). ¿Cuál sería tal implementación en Map. No tiene sentido tener un método así Map. En cambio tenemos un put(key,value)método en Map.

Mismo argumento se aplica a addAll(), remove()y removeAll()métodos. Entonces, la razón principal es la diferencia en la forma en que los datos se almacenan en Mapy Collection. Además, si recuerda la Collectioninterfaz implementada Iterable, es decir, cualquier interfaz con .iterator()método debe devolver un iterador que debe permitirnos iterar sobre los valores almacenados en el Collection. Ahora, ¿qué devolvería tal método para a Map? ¿Un iterador clave o un iterador de valor? Esto tampoco tiene sentido.

Hay formas en que podemos iterar sobre las claves y los almacenes de valores en ay Mapasí es como es parte del Collectionmarco.

Mayur Ingle
fuente
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.¿Puedes mostrar un código de ejemplo para esto?
RamenChef
Eso estuvo muy bien explicado. Ahora lo entiendo. ¡Gracias!
Emir Memic
Una implementación de add(Object o)sería agregar un Entry<ClassA, ClassB>objeto. A Mappuede considerarse unCollection of Tuples
LIvanov
0

¡Exactamente, interface Map<K,V> extends Set<Map.Entry<K,V>>sería genial!

En realidad, si lo fuera implements Map<K,V>, Set<Map.Entry<K,V>>, entonces tiendo a estar de acuerdo ... Parece incluso natural. Pero eso no funciona muy bien, ¿verdad? Digamos que tenemos HashMap implements Map<K,V>, Set<Map.Entry<K,V>, LinkedHashMap implements Map<K,V>, Set<Map.Entry<K,V>etc ... eso está bien, pero si lo hubiera hecho entrySet(), nadie olvidará implementar ese método, y puede estar seguro de que puede obtener entrySet para cualquier Mapa, mientras que no lo está si espera que el implementador ha implementado ambas interfaces ...

La razón por la que no quiero tener interface Map<K,V> extends Set<Map.Entry<K,V>>es simplemente, porque habrá más métodos. Y después de todo, son cosas diferentes, ¿verdad? También muy prácticamente, si llego map.a IDE, no quiero ver .remove(Object obj), y .remove(Map.Entry<K,V> entry)porque no puedo hacer hit ctrl+space, r, returny terminar con eso.

Enno Shioji
fuente
Me parece que si uno pudiera afirmar, semánticamente, que "Un mapa es un conjunto en el mapa. Entrada", entonces uno se molestaría en implementar el método relevante; y si uno no puede hacer esa declaración, entonces uno no lo haría, es decir, debería ser sobre la molestia.
einpoklum
0

Map<K,V>no debe extenderse Set<Map.Entry<K,V>>ya que:

  • No puede agregar diferentes correos Map.Entryelectrónicos con la misma clave a la misma Map, pero
  • Usted puede agregar diferentes Map.Entrys con la misma clave a la misma Set<Map.Entry>.
einpoklum
fuente
... esta es una declaración sucinta de la historia de la segunda parte de la respuesta de @cletus.
einpoklum
-2

Recto y simple. Collection es una interfaz que solo espera un Object, mientras que Map requiere Two.

Collection(Object o);
Map<Object,Object>
Trinadh Koya
fuente
Nice Trinad, tus respuestas siempre son simples y cortas.
Sairam