Java API de colecciones: ¿por qué no se puede modificar [Listado | Set | Mapa] no manifiestamente visibles clases?
-
28-09-2019 - |
Pregunta
Collections.unmodifiableList(...)
devuelve una nueva instancia de un clase interna estática UnmodifiableList
. Otras clases de colecciones no modificables se construyen misma manera.
fueron estas clases pública, uno tenía dos ventajas:
- capacidad de indicar un valor de retorno específica más (como
UnmodifiableList
), por lo que un usuario de la API no vendría a la idea de modificar esa colección; - capacidad de comprobar en tiempo de ejecución si un
List
esinstanceof UnmodifiableList
.
Por lo tanto, estaban allí ninguna ventaja no para hacer esas clases público?
Editar . No se presentaron argumentos convincentes, sin duda, así que elegir la respuesta más upvoted
Solución
Hay las dos ventajas están ahí, pero no son tan útiles. Los principales problemas siguen siendo los mismos: UnmodifiableList todavía es una lista y así todos los emisores están disponibles y las colecciones subyacentes todavía son modificables. Hacer público UnmodifiableList clase se sumaría a la ilusión de ser inmodificable.
La forma más agradable sería que el compilador de ayuda, pero para que las jerarquías de clases colección tendrían que cambiado mucho. Por ejemplo, la API colección de Scala es mucho más avanzado en ese sentido.
Una desventaja sería la introducción de al menos tres clases / interfaces adicionales en el API. Debido a ellos no ser tan útil, creo que lo que les deja fuera de la API es una opción buena.
Otros consejos
En lo personal estoy totalmente de acuerdo con usted. En el núcleo del problema es el hecho de que los genéricos de Java no son covariantes, los cuales, a su vez, se debe a que las colecciones de Java son mutables.
No es posible para el sistema de tipo de Java para codificar un tipo que parece tener mutators es realidad inmutable . Imagínese si tuviéramos que empezar 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 a partir ImmutableMap
por lo que cualquier método que devuelve un mapa tales inmutable:
public ImmutableMap<K, V> foo();
puede ser asignada a un mapa y por lo tanto puede ser mutado en tiempo de compilación:
Map<K, V> m = foo();
m.put(k, v); //oh dear
Por lo tanto, se puede ver que la adición de este tipo no ha hecho nos ha impedido hacer nada malo. Creo que por esta razón se hizo justicia que no tiene suficiente para ofrecer.
Un lenguaje como Scala tiene la declaración de sitio anotaciones de varianza. Es decir, se puede especificar un tipo como covariante (y por tanto inmutables) como Mapa de Scala es (en realidad es covariante en su parámetro V
). De ahí su API puede declarar si su tipo de retorno es mutable o inmutable.
Como otro lado, Scala permite declarar tipos de intersección de modo que usted ni siquiera necesita para crear la interfaz ImmutableXYZ
como una entidad separada, que podría indicar un procedimiento de retorno:
def foo : XYZ with Immutable
Pero Scala tiene entonces un sistema de tipo adecuado, mientras que Java no
Si es importante para que usted compruebe si la lista se creó con Collections.unmodifiableList entonces se puede crear una instancia y pedir la clase. Ahora se puede 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() ){
...
}
La respuesta a la razón por la es bastante simple: en el momento, en 1998, era un diseño eficiente flanky bits. La gente pensaba de él no fue al parecer una prioridad. Pero no había ningún verdadero pensamiento profundo, al respecto.
Si desea utilizar un mecanismo de este tipo, utilice Guava ImmutableList / Set / Mapa /...
Ellos son explícitamente inmutable y una práctica buena cuando se utiliza esa biblioteca no es para devolver una lista, por ejemplo, sino una ImmutableList. Así sabrá que una Lista / Set / Mapa / ... es inmutable.
Ejemplo:
private final ImmutableList constants = ...;
public final ImmutableList<String> getConstants() {
return constants;
}
Sobre el diseño mismo 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
...
}
...
}
- capacidad de indicar un valor de retorno específica más (como
UnmodifiableList
), por lo que un usuario de la API no vendría a la idea de modificar esa colección;
En una API adecuada, esto ya debería estar documentada en el Javadoc del método de devolución de la lista no se puede modificar.
- capacidad de comprobar en tiempo de ejecución si un
List
esinstanceof UnmodifiableList
.
Tal necesidad indica que los mentiras reales de problemas en otro lugar. Es un defecto en el diseño de código. Pregúntese, ha tenido alguna vez la necesidad de comprobar si un List
es una instancia de ArrayList
o LinkedList
? Ya se trate de un ArrayList
, LinkedList
o UnmodifiableList
es claramente una decisión que se va a realizar durante el tiempo de escribir código, no durante el tiempo de ejecución de código. Si los problemas persisten, ya que está intentando modificar una UnmodifiableList
(para el cual el desarrollador API puede tener muy buenas reasions que debe ser ya documentadas), entonces es más bien su propia culpa, no es un fallo de tiempo de ejecución.
Todos con todo, no tiene sentido. El Collections#unmodifiableXXX()
, synchronizedXXX()
y checkedXXX()
en modo alguno no representan implementaciones concretas. Todos ellos son simplemente decoradores que se puede aplicar independientemente del hormigón subyacente aplicación.
Supongamos UnmodifiableList
era una clase pública. Sospecho que sería calmar a los programadores en una falsa sensación de seguridad. Recuerde, UnmodifiableList
es una vista de un modificable List
. Esto significa que el contenido de un UnmodifiableList
todavía puede cambiar a través de los cambios realizados en su List
subyacente. Un programador ingenuo no puede entender este matiz y puede esperar que las instancias de UnmodifiableList
que son inmutables.
Creo que la respuesta se debe a la forma adecuada método sabe acerca de los medicamentos genéricos utilizados y no requiere programación adicional para pasar esta información a través, mientras que el formulario de la clase requeriría más perder el tiempo. La forma método para unmodifiableMap
tiene dos argumentos genéricos flotantes, que se asigna a ambos los argumentos genéricos del tipo de retorno y del argumento pasado.
public static <K,V> Map<K,V> unmodifiableMap(Map<? extends K, ? extends V> m) {
return new UnmodifiableMap<K,V>(m);
}