tipos - API de colecciones de Java: ¿por qué las clases[Lista | Establecer | Mapa] no son modificables?
tipos de collections (7)
Collections.unmodifiableList(...)
devuelve una nueva instancia de una clase interna estática UnmodifiableList
. Otras clases de colecciones no modificables se construyen de la misma manera.
Si estas clases fueran públicas, una tenía dos ventajas:
- capacidad de indicar un valor de retorno más específico (como
UnmodifiableList
), por lo que un usuario de la API no tendría la idea de modificar esa colección; - capacidad de verificar durante el tiempo de ejecución si una
List
esinstanceof UnmodifiableList
.
Entonces, ¿había alguna ventaja de no hacer públicas esas clases?
EDITAR : No se presentaron argumentos definitivamente convincentes, entonces elijo la respuesta más votada.
- capacidad de indicar un valor de retorno más específico (como
UnmodifiableList
), por lo que un usuario de la API no tendría la idea de modificar esa colección;
En una API adecuada, esto ya debería estar documentado en el javadoc del método que devuelve la lista no modificable.
- capacidad de verificar durante el tiempo de ejecución si una
List
esinstanceof UnmodifiableList
.
Tal necesidad indica que el problema real yace en otro lugar. Es un defecto en el diseño del código. Pregúntese, ¿alguna vez tuvo la necesidad de verificar si una List
es una instancia de ArrayList
o LinkedList
? Si se trata de una ArrayList
, LinkedList
o UnmodifiableList
es claramente una decisión que se tomará durante el tiempo de escritura del código, no durante el tiempo de ejecución del código. Si está teniendo problemas porque está intentando modificar UnmodifiableList
(para lo cual el desarrollador de la API puede tener muy buenas explicaciones que ya deberían estar documentadas), entonces es su propia culpa, no una falla en el tiempo de ejecución.
Con todo, no tiene sentido. Las Collections#unmodifiableXXX()
, synchronizedXXX()
y checkedXXX()
no representan en modo alguno las implementaciones concretas. Todos ellos son decorators que se pueden aplicar independientemente de la implementación concreta subyacente.
Creo que ambas ventajas están ahí, pero no son tan útiles. Los principales problemas siguen siendo los mismos: UnmodifiableList aún es una lista y, por lo tanto, todos los organismos de creación están disponibles y las colecciones subyacentes aún son modificables. Hacer que la clase UnmodifiableList público se agregue a la ilusión de ser inmodificable.
La forma más agradable sería que el compilador ayudara, pero para eso las jerarquías de la clase de colección tendrían que cambiar mucho. Por ejemplo, la API de colección de Scala es mucho más avanzada en ese sentido.
Una desventaja sería la introducción de al menos tres clases / interfaces adicionales en la API. Debido a que no son tan útiles, creo que dejarlos fuera de la API es una buena opción.
Creo que la respuesta es porque el método de forma adecuada sabe sobre los genéricos utilizados y no requiere programación adicional para pasar esta información, mientras que la forma de clase requeriría más lío. La forma del método unmodifiableMap
tiene dos argumentos genéricos flotantes, que se correlaciona con los argumentos genéricos del tipo de devolución y del argumento pasado.
public static <K,V> Map<K,V> unmodifiableMap(Map<? extends K, ? extends V> m) {
return new UnmodifiableMap<K,V>(m);
}
La respuesta al por qué es bastante simple: en ese momento, en 1998, el diseño eficiente era un poco complicado. La gente lo pensó, aparentemente no era una prioridad. Pero no había un pensamiento verdadero y profundo al respecto.
Si desea utilizar dicho mecanismo, use la Lista Inmutable / Set / Map / ... de Guava
Son explícitamente inmutables y una buena práctica al usar esa biblioteca es no devolver una lista, por ejemplo, sino una ImmutableList. Entonces sabrá que una Lista / Conjunto / Mapa / ... es inmutable.
Ejemplo:
private final ImmutableList constants = ...;
public final ImmutableList<String> getConstants() {
return constants;
}
Acerca del diseño de UnmodifiableXxx, uno podría haber hecho lo siguiente:
public static final class UnmodifiableXxx implements Xxx { // don''t allow extend
// static if inside Collections
UnmodifiableXxx (Xxx backend) { // don''t allow direct instanciation
...
}
...
}
Personalmente, estoy completamente de acuerdo contigo. En el núcleo del problema está el hecho de que los genéricos de Java no son covariantes, lo que a su vez se debe a que las colecciones de Java son mutables.
No es posible que el sistema de tipos de Java para codificar un tipo que parece tener mutadores es realmente inmutable . Imagínese si tuviéramos que comenzar a diseñar alguna solución:
interface Immutable //marker for immutability
interface ImmutableMap<K, V> extends Map<K, V>, Immutable
Pero entonces ImmutableMap
es una subclase de Map
, y por lo tanto, Map
es asignable desde ImmutableMap
por lo que cualquier método que devuelva un Mapa tan inmutable:
public ImmutableMap<K, V> foo();
se puede asignar a un mapa y, por lo tanto, se puede mutar en el momento de la compilación:
Map<K, V> m = foo();
m.put(k, v); //oh dear
Entonces, pueden ver que la adición de este tipo en realidad no nos ha impedido hacer algo malo. Creo que por esta razón se hizo un juicio que no tenía lo suficiente para ofrecer.
Un lenguaje como Scala tiene anotaciones de varianza en el sitio de declaración. Es decir, podría especificar un tipo como covariante (y, por tanto, inmutable) como es el Mapa de Scala (en realidad es covariante en su parámetro V
). Por lo tanto, su API puede declarar si su tipo de devolución es mutable o inmutable.
Como otro lado, Scala te permite declarar los tipos de intersección para que no necesites crear la interfaz ImmutableXYZ
como una entidad separada, puedes especificar un método para regresar:
def foo : XYZ with Immutable
Pero entonces Scala tiene un sistema de tipo apropiado, mientras que Java no
Si es importante que compruebe si la lista se creó con Collections.unmodifiableList, puede crear una instancia y solicitar la clase. Ahora puedes comparar esta clase con la clase de cualquier lista.
private static Class UNMODIFIABLE_LIST_CLASS =
Collections.unmodifiableList( new ArrayList() ).getClass();
...
if( UNMODIFIABLE_LIST_CLASS == listToTest.getClass() ){
...
}
Supongamos que UnmodifiableList
fuera una clase pública. Sospecho que calmaría a los programadores con una falsa sensación de seguridad. Recuerde, UnmodifiableList
es una vista de una List
modificable . Esto significa que el contenido de UnmodifiableList
aún puede cambiar a través de cualquier cambio realizado en su List
subyacente. Un programador ingenuo puede no entender este matiz y puede esperar instancias de UnmodifiableList
para ser inmutable.