En el pasado, he dicho que copiar una colección de manera segura haga algo como:
public static void doThing(List<String> strs) {
List<String> newStrs = new ArrayList<>(strs);
o
public static void doThing(NavigableSet<String> strs) {
NavigableSet<String> newStrs = new TreeSet<>(strs);
Pero, ¿son estos constructores de "copia", métodos y flujos de creación estáticos similares, realmente seguros y dónde se especifican las reglas? Por seguro, quiero decir, son las garantías básicas de integridad semántica ofrecidas por el lenguaje Java y las colecciones aplicadas contra un llamador malicioso, suponiendo SecurityManager
que esté respaldado por un razonable y que no haya fallas.
Estoy contento con el lanzamiento método ConcurrentModificationException
, NullPointerException
, IllegalArgumentException
, ClassCastException
, etc, o tal vez incluso colgado.
He elegido String
como ejemplo un argumento de tipo inmutable. Para esta pregunta, no me interesan las copias profundas para colecciones de tipos mutables que tienen sus propios trucos.
(Para ser claros, he mirado el código fuente de OpenJDK y tengo algún tipo de respuesta para ArrayList
y TreeSet
).
fuente
NavigableSet
y otrasComparable
colecciones basadas a veces pueden detectar si una clase no se implementacompareTo()
correctamente y lanzar una excepción. No está claro qué quiere decir con argumentos no confiables. ¿Te refieres a un malhechor que crea una colección de cadenas malas y cuando las copias a tu colección, algo malo sucede? No, el marco de colecciones es bastante sólido, existe desde 1.2.HashSet
(y todas las demás colecciones de hash en general) se basa en la corrección / integridad de lahashCode
implementación de los elementos,TreeSet
yPriorityQueue
depende deComparator
(y ni siquiera puede crea una copia equivalente sin aceptar el comparador personalizado si lo hay),EnumSet
confía en la integridad delenum
tipo particular que nunca se verifica después de la compilación, por lo que un archivo de clase, no generadojavac
o hecho a mano, puede subvertirlo.new TreeSet<>(strs)
dóndestrs
está aNavigableSet
. Esta no es una copia masiva, ya que el resultadoTreeSet
utilizará el comparador de la fuente, que incluso es necesario para mantener la semántica. Si está bien con solo procesar los elementos contenidos, estetoArray()
es el camino a seguir; incluso mantendrá el orden de iteración. Cuando está bien con "tomar elemento, validar elemento, usar elemento", ni siquiera necesita hacer una copia. Los problemas comienzan cuando desea verificar todos los elementos, seguido de la utilización de todos los elementos. Entonces, no puede confiar en unaTreeSet
copia con un comparador personalizadocheckcast
para cada elemento, estoArray
con un tipo específico. Siempre terminamos en eso. Las colecciones genéricas ni siquiera conocen su tipo de elemento real, por lo que sus constructores de copias no pueden proporcionar una funcionalidad similar. Por supuesto, puede diferir cualquier verificación al uso previo correcto, pero entonces, no sé a qué apuntan sus preguntas. No necesita "integridad semántica", cuando está de acuerdo con verificar y fallar inmediatamente antes de usar elementos.Respuestas:
No existe una protección real contra el código intencionalmente malicioso que se ejecuta dentro de la misma JVM en las API normales, como la API de recopilación.
Como se puede demostrar fácilmente:
Como puede ver, esperar un
List<String>
no garantiza realmente obtener una lista deString
instancias. Debido a la eliminación de tipos y tipos sin formato, ni siquiera hay una solución posible en el lado de implementación de la lista.La otra cosa de la que puede culpar
ArrayList
al constructor es la confianza en latoArray
implementación de la colección entrante .TreeMap
no se ve afectado de la misma manera, sino solo porque no hay tal ganancia de rendimiento al pasar la matriz, como en la construcción de unArrayList
. Ninguna clase garantiza una protección en el constructor.Normalmente, no tiene sentido intentar escribir código suponiendo código intencionalmente malicioso en cada esquina. Hay demasiado que puede hacer para protegerse contra todo. Dicha protección solo es útil para el código que realmente encapsula una acción que podría otorgarle a una persona que llama maliciosa acceso a algo, ya que no podría acceder sin este código.
Si necesita seguridad para un código en particular, use
Luego, puede estar seguro de que
newStrs
solo contiene cadenas y no puede ser modificado por otro código después de su construcción.O úselo
List<String> newStrs = List.of(strs.toArray(new String[0]));
con Java 9 o más recienteTenga en cuenta que Java 10's
List.copyOf(strs)
hace lo mismo, pero su documentación no indica que está garantizado que no confíe en eltoArray
método de la colección entrante . EntoncesList.of(…)
, llamar , que definitivamente hará una copia en caso de que devuelva una lista basada en una matriz, es más seguro.Como ninguna persona que llama puede alterar la forma, las matrices funcionan, volcar la colección entrante en una matriz, seguido de llenar la nueva colección con ella, siempre hará que la copia sea segura. Dado que la colección puede contener una referencia a la matriz devuelta como se demostró anteriormente, podría alterarla durante la fase de copia, pero no puede afectar la copia en la colección.
Por lo tanto, cualquier verificación de coherencia debe realizarse después de que el elemento particular se haya recuperado de la matriz o de la colección resultante en su conjunto.
fuente
AccessController.doPrivileged(…)
etc. Pero la larga lista de errores relacionados con la seguridad del applet nos da una pista de por qué se ha abandonado esta tecnología ...new ArrayList<>(…)
como constructor de copias está bien asumiendo implementaciones de colección correctas. No es su deber solucionar los problemas de seguridad cuando ya es demasiado tarde. ¿Qué pasa con el hardware comprometido? ¿El sistema operativo? ¿Qué tal multihilo?List.copyOf(strs)
no se basa en la corrección de la colección entrante en ese sentido, al precio obvio.ArrayList
es un compromiso razonable para todos los días.toArray()
sí mismo, porque las matrices no pueden tener un comportamiento anulado, seguido de la creación de una copia de recopilación de la matriz, comonew ArrayList<>(Arrays.asList( strs.toArray(new String[0])))
oList.of(strs.toArray(new String[0]))
. Ambos también tienen el efecto secundario de aplicar el tipo de elemento. Yo, personalmente, no creo que alguna vez permitancopyOf
comprometer las colecciones inmutables, pero las alternativas están ahí, en la respuesta.Preferiría dejar esta información en el comentario, pero no tengo suficiente reputación, lo siento :) Trataré de explicarla tan detalladamente como pueda.
En lugar de algo así como un
const
modificador utilizado en C ++ para marcar funciones miembro que no deben modificar el contenido del objeto, en Java originalmente se utilizó el concepto de "inmutabilidad". Se suponía que la encapsulación (u OCP, principio abierto-cerrado) protegía contra cualquier mutación inesperada (cambio) de un objeto. Por supuesto, la API de reflexión explica esto; el acceso directo a la memoria hace lo mismo; eso es más sobre disparar una pierna :)java.util.Collection
en sí es una interfaz mutable: tiene unadd
método que se supone que modifica la colección. Por supuesto, el programador puede envolver la colección en algo que arrojará ... y todas las excepciones de tiempo de ejecución sucederán porque otro programador no pudo leer javadoc, que claramente dice que la colección es inmutable.Decidí usar
java.util.Iterable
type para exponer una colección inmutable en mis interfaces. SemánticamenteIterable
no tiene una característica de colección como la "mutabilidad". Aún así (muy probablemente) podrá modificar colecciones subyacentes a través de secuencias.java.util.Function<K,V>
Se puede usar JIC para exponer mapas de manera inmutable (elget
método del mapa se ajusta a esta definición)fuente
Iterator
tuviera que pasar un entonces, eso prácticamente obliga a una copia por elementos, pero eso no es bueno. UsarforEachRemaining
/forEach
obviamente será un completo desastre. (También tengo que mencionar queIterator
tiene unremove
método.)Iterable
no sea realmente inmutable, pero no veo ningún problema conforEach*
)