ventajas objetos objeto mutables modificar inmutables inmutable inmutabilidad estructuras clases java functional-programming immutability

modificar - ¿Cómo identifico objetos inmutables en Java?



objetos mutables e inmutables java (15)

En mi código, estoy creando una colección de objetos a la que accederán varios hilos de una forma que solo es segura si los objetos son inmutables. Cuando se intenta insertar un nuevo objeto en mi colección, quiero probar para ver si es inmutable (si no, lanzaré una excepción).

Una cosa que puedo hacer es verificar algunos tipos inmutables bien conocidos:

private static final Set<Class> knownImmutables = new HashSet<Class>(Arrays.asList( String.class, Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class, Boolean.class, BigInteger.class, BigDecimal.class )); ... public static boolean isImmutable(Object o) { return knownImmutables.contains(o.getClass()); }

Esto realmente me da el 90% del camino, pero a veces mis usuarios querrán crear sus propios tipos inmutables:

public class ImmutableRectangle { private final int width; private final int height; public ImmutableRectangle(int width, int height) { this.width = width; this.height = height; } public int getWidth() { return width; } public int getHeight() { return height; } }

¿Hay alguna manera (tal vez usando el reflejo) de poder detectar de manera confiable si una clase es inmutable? Los falsos positivos (pensando que es inmutable cuando no lo es) no son aceptables, pero los falsos negativos (pensando que es mutable cuando no lo es) sí lo son.

Editado para agregar: gracias por las respuestas perspicaces y útiles. Como señalaron algunas de las respuestas, omití definir mis objetivos de seguridad. La amenaza aquí son los desarrolladores desorientados: se trata de una pieza de código de marco que será utilizada por un gran número de personas que saben casi todo sobre el enhebrado y no leerán la documentación. NO necesito defenderme contra desarrolladores maliciosos: cualquier persona lo suficientemente inteligente como para mutar un String o realizar otros chanchullos también será lo suficientemente inteligente como para saber que no es seguro en este caso. El análisis estático de la base de código ES una opción, siempre que esté automatizado, pero no se puede contar con las revisiones de código porque no hay garantía de que cada revisión cuente con revisores expertos en threading.


En mi código, estoy creando una colección de objetos a la que accederán varios hilos de una forma que solo es segura si los objetos son inmutables.

No es una respuesta directa a su pregunta, pero tenga en cuenta que no se garantiza automáticamente que los objetos que son inmutables sean seguros para subprocesos (por desgracia). El código necesita ser libre de efectos secundarios para ser seguro para subprocesos, y eso es bastante más difícil.

Supongamos que tiene esta clase:

class Foo { final String x; final Integer y; ... public bar() { Singleton.getInstance().foolAround(); } }

Entonces, el método foolAround() podría incluir algunas operaciones que no sean seguras para hilos, lo que explotará tu aplicación. Y no es posible probar esto utilizando la reflexión, ya que la referencia real solo se puede encontrar en el cuerpo del método, no en los campos ni en la interfaz expuesta.

Aparte de eso, los otros son correctos: puedes escanear todos los campos declarados de la clase, verificar si cada uno de ellos es final y también una clase inmutable, y listo. No creo que los métodos sean definitivos es un requisito.

Además, tenga cuidado con la comprobación recursiva de los campos dependientes por inmutabilidad, puede terminar con círculos:

class A { final B b; // might be immutable... } class B { final A a; // same so here. }

Las clases A y B son perfectamente inmutables (y posiblemente incluso utilizables a través de algunos hacks de reflexión), pero el código recursivo ingenuo entrará en un bucle infinito comprobando A, luego B, luego A nuevamente, en adelante a B, ...

Puedes arreglar eso con un mapa "visto" que no permite ciclos, o con algún código realmente inteligente que decida que las clases son inmutables si todos sus valores son inmutables solo en función de ellos mismos, pero eso va a ser realmente complicado ...


¿Por qué todas las recomendaciones requieren que la clase sea final? si está utilizando la reflexión para verificar la clase de cada objeto, y puede determinar mediante programación que esa clase es inmutable (campos inmutables, finales), entonces no necesita exigir que la clase en sí sea definitiva.


Al igual que los otros contestadores ya dijo, en mi humilde opinión no hay manera confiable de averiguar si un objeto es realmente inmutable.

Simplemente presentaría una interfaz "Inmutable" para verificar cuando se agregue. Esto funciona como una sugerencia de que solo se deben insertar objetos inmutables por cualquier razón que lo esté haciendo.

interface Immutable {} class MyImmutable implements Immutable{...} public void add(Object o) { if (!(o instanceof Immutable) && !checkIsImmutableBasePrimitive(o)) throw new IllegalArgumentException("o is not immutable!"); ... }


Algo que funciona para un alto porcentaje de clases integradas es prueba por ejemplo de Comparable. Para las clases que no son inmutables como Date, a menudo se las trata como inmutables en la mayoría de los casos.


Aprecio y admiro la cantidad de trabajo que Grundlefleck ha puesto en su detector de mutabilidad, pero creo que es un poco exagerado. Puede escribir un detector simple pero prácticamente muy adecuado (es decir, pragmático ) de la siguiente manera:

(nota: esta es una copia de mi comentario aquí: https://.com/a/28111150/773113 )

En primer lugar, no se limitará a escribir un método que determine si una clase es inmutable; en su lugar, tendrá que escribir una clase de detector de inmutabilidad, porque tendrá que mantener algún estado. El estado del detector será la inmutabilidad detectada de todas las clases que ha examinado hasta ahora. Esto no solo es útil para el rendimiento, sino que es realmente necesario porque una clase puede contener una referencia circular, lo que provocaría que un detector de inmutabilidad simplista cayera en una recursión infinita.

La inmutabilidad de una clase tiene cuatro valores posibles: Unknown , Mutable , Immutable y Calculating . Probablemente desees tener un mapa que asocie cada clase que has encontrado hasta ahora con un valor de inmutabilidad. Por supuesto, Unknown realidad no necesita ser implementado, ya que será el estado implícito de cualquier clase que aún no esté en el mapa.

Entonces, cuando comienzas a examinar una clase, la asocias con un valor de Calculating en el mapa, y cuando terminas, reemplazas Calculating con Immutable o Mutable .

Para cada clase, solo necesita verificar los miembros del campo, no el código. La idea de verificar bytecode es bastante errónea.

Antes que nada, no debes verificar si una clase es final; La finalidad de una clase no afecta su inmutabilidad. En cambio, un método que espera un parámetro inmutable debería invocar primero al detector de inmutabilidad para afirmar la inmutabilidad de la clase del objeto real que se pasó. Esta prueba se puede omitir si el tipo del parámetro es una clase final, por lo que la finalidad es buena para el rendimiento, pero estrictamente hablando no es necesario. Además, como verá más adelante, un campo cuyo tipo es de una clase no final causará que la clase declarante se considere mutable, pero aún así, ese es un problema de la clase que declara, no el problema del no final clase de miembro inmutable. Está perfectamente bien tener una alta jerarquía de clases inmutables, en la que todos los nodos de hoja no deben ser definitivos.

No debe verificar si un campo es privado; está perfectamente bien para una clase tener un campo público, y la visibilidad del campo no afecta la inmutabilidad de la clase declarante de ninguna manera o forma. Solo necesita comprobar si el campo es definitivo y su tipo es inmutable.

Al examinar una clase, lo primero que debes hacer es recurrir para determinar la inmutabilidad de su super . Si el súper es mutable, entonces el descendiente también es mutable por definición.

Entonces, solo necesita verificar los campos declarados de la clase, no todos los campos.

Si un campo no es final, entonces tu clase es mutable.

Si un campo es final, pero el tipo del campo es mutable, entonces tu clase es mutable. (Las matrices son, por definición, mutables).

Si un campo es final y el tipo del campo es Calculating , entonces ignórelo y proceda al siguiente campo. Si todos los campos son inmutables o Calculating , su clase es inmutable.

Si el tipo del campo es una interfaz, o una clase abstracta, o una clase no final, entonces debe considerarse como mutable, ya que no tiene absolutamente ningún control sobre lo que la implementación real puede hacer. Esto podría parecer un problema insuperable, ya que significa que el ajuste de una colección modificable dentro de UnmodifiableCollection aún no pasará la prueba de inmutabilidad, pero en realidad está bien, y se puede manejar con la siguiente solución.

Algunas clases pueden contener campos no finales y aún ser efectivamente inmutables . Un ejemplo de esto es la clase String . Otras clases que entran en esta categoría son las clases que contienen miembros no finales puramente para propósitos de monitoreo de rendimiento (contadores de invocación, etc.), clases que implementan la inmutabilidad de paleta ( búscalo ) y clases que contienen miembros que son interfaces que son conocidas para no causar ningún efecto secundario. Además, si una clase contiene campos mutables de buena fe pero promete no tenerlos en cuenta al calcular hashCode () y equals (), entonces la clase es, por supuesto, insegura cuando se trata de multi-threading, pero aún se puede considerar como inmutable con el propósito de usarlo como clave en un mapa. Entonces, todos estos casos se pueden manejar de una de estas dos formas:

  1. Agregando manualmente clases (e interfaces) a su detector de inmutabilidad. Si sabe que cierta clase es efectivamente inmutable a pesar de que la prueba de inmutabilidad falla, puede agregar manualmente una entrada a su detector que lo asocia con Immutable . De esta forma, el detector nunca intentará verificar si es inmutable, siempre solo dirá ''sí, así es''.

  2. Presentamos una anotación @ImmutabilityOverride . Su detector de inmutabilidad puede verificar la presencia de esta anotación en un campo, y si está presente, puede tratar el campo como inmutable a pesar de que el campo puede ser no final o su tipo puede ser mutable. El detector también puede verificar la presencia de esta anotación en la clase, tratando así a la clase como inmutable sin siquiera molestarse en verificar sus campos.

Espero que esto ayude a las futuras generaciones.


Básicamente no.

Podrías construir una lista blanca gigante de clases aceptadas, pero creo que la forma menos loca sería escribir en la documentación de la colección que todo lo que pasa es que esta colección debe ser inmutable.

Editar: otras personas han sugerido tener una anotación inmutable. Esto está bien, pero también necesita la documentación. De lo contrario, la gente simplemente pensará "si pongo esta anotación en mi clase, puedo guardarla en la colección" y simplemente la arrojaré sobre cualquier cosa, tanto las clases inmutables como mutables. De hecho, sería cauteloso de tener una anotación inmutable solo en caso de que la gente piense que la anotación hace que su clase sea inmutable.


Echa un vistazo a joe-e , una implementación de capacidades para java.


En mi empresa, hemos definido un atributo llamado @Immutable . Si eliges adjuntar eso a una clase, significa que prometes que eres inmutable.

Funciona para la documentación, y en su caso funcionaría como un filtro.

Por supuesto, todavía depende de que el autor cumpla su palabra acerca de ser inmutable, pero dado que el autor agregó explícitamente la anotación, es una suposición razonable.


Esta podría ser otra pista:

Si la clase no tiene setters, entonces no puede mutarse, si los parámetros con los que se creó son de tipo "primitivo" o no mutables.

Además, no se pueden sobrescribir ningún método, todos los campos son finales y privados,

Intentaré codificar algo mañana para ti, pero el código de Simon usando el reflejo se ve bastante bien.

Mientras tanto, intente obtener una copia del libro "Java efectivo" de Josh Block, tiene un artículo relacionado con este tema. Si bien no es seguro decir cómo detectar una clase inmutable, muestra cómo crear una buena.

El artículo se llama: "Favorecer la inmutabilidad"

enlace: http://java.sun.com/docs/books/effective/


No hay una manera confiable de detectar si una clase es inmutable. Esto se debe a que hay muchas maneras en que se puede modificar una propiedad de una clase y no se pueden detectar todas a través de la reflexión.

La única forma de acercarte a esto es:

  • Solo permite propiedades finales de tipos que son inmutables (tipos primitivos y clases que sabes que son inmutables),
  • Requerir que la clase sea definitiva
  • Requerir que hereden de una clase base que proporcionas (que se garantiza que es inmutable)

Luego puede verificar con el siguiente código si el objeto que tiene es inmutable:

static boolean isImmutable(Object obj) { Class<?> objClass = obj.getClass(); // Class of the object must be a direct child class of the required class Class<?> superClass = objClass.getSuperclass(); if (!Immutable.class.equals(superClass)) { return false; } // Class must be final if (!Modifier.isFinal(objClass.getModifiers())) { return false; } // Check all fields defined in the class for type and if they are final Field[] objFields = objClass.getDeclaredFields(); for (int i = 0; i < objFields.length; i++) { if (!Modifier.isFinal(objFields[i].getModifiers()) || !isValidFieldType(objFields[i].getType())) { return false; } } // Lets hope we didn''t forget something return true; } static boolean isValidFieldType(Class<?> type) { // Check for all allowed property types... return type.isPrimitive() || String.class.equals(type); }

Actualización: como se sugirió en los comentarios, podría extenderse a recurse en la superclase en lugar de buscar cierta clase. También se sugirió usar recursivamente isImmutable en el método isValidFieldType. Esto probablemente podría funcionar y también he hecho algunas pruebas. Pero esto no es trivial. No se puede simplemente verificar todos los tipos de campo con una llamada a isImmutable, porque String ya no pasa esta prueba (¡su hash campo no es definitivo!). También se está ejecutando fácilmente recurrencias sin fin, causando Errors ;) Otros problemas pueden ser causados ​​por los genéricos, donde también debe comprobar sus tipos de inmutablity.

Creo que con algún trabajo, estos problemas potenciales podrían resolverse de alguna manera. Pero entonces, primero debe preguntarse si realmente vale la pena (también en cuanto al rendimiento).


Prueba esto:

public static boolean isImmutable(Object object){ if (object instanceof Number) { // Numbers are immutable if (object instanceof AtomicInteger) { // AtomicIntegers are mutable } else if (object instanceof AtomicLong) { // AtomLongs are mutable } else { return true; } } else if (object instanceof String) { // Strings are immutable return true; } else if (object instanceof Character) { // Characters are immutable return true; } else if (object instanceof Class) { // Classes are immutable return true; } Class<?> objClass = object.getClass(); // Class must be final if (!Modifier.isFinal(objClass.getModifiers())) { return false; } // Check all fields defined in the class for type and if they are final Field[] objFields = objClass.getDeclaredFields(); for (int i = 0; i < objFields.length; i++) { if (!Modifier.isFinal(objFields[i].getModifiers()) || !isImmutable(objFields[i].getType())) { return false; } } // Lets hope we didn''t forget something return true; }


Puede solicitar a sus clientes que agreguen metadatos (anotaciones) y que los comprueben en tiempo de ejecución con reflexión, como este:

Metadatos:

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.CLASS) public @interface Immutable{ }

Codigo del cliente:

@Immutable public class ImmutableRectangle { private final int width; private final int height; public ImmutableRectangle(int width, int height) { this.width = width; this.height = height; } public int getWidth() { return width; } public int getHeight() { return height; } }

Luego, al usar la reflexión en la clase, compruebe si tiene la anotación (pegaría el código pero es una repetición y se puede encontrar fácilmente en línea)


Puede usar anotación AOP y @Immutable desde aspectos jcabi :

@Immutable public class Foo { private String data; } // this line will throw a runtime exception since class Foo // is actually mutable, despite the annotation Object object = new Foo();


Que yo sepa, no hay forma de identificar objetos inmutables que sea 100% correcto. Sin embargo, he escrito una biblioteca para acercarte. Realiza análisis de bytecode de una clase para determinar si es inmutable o no, y puede ejecutar en tiempo de ejecución. Está en el lado estricto, por lo que también permite incluir en la lista blanca las clases inmutables conocidas.

Puede verificarlo en: www.mutabilitydetector.org

Le permite escribir código como este en su aplicación:

/* * Request an analysis of the runtime class, to discover if this * instance will be immutable or not. */ AnalysisResult result = analysisSession.resultFor(dottedClassName); if (result.isImmutable.equals(IMMUTABLE)) { /* * rest safe in the knowledge the class is * immutable, share across threads with joyful abandon */ } else if (result.isImmutable.equals(NOT_IMMUTABLE)) { /* * be careful here: make defensive copies, * don''t publish the reference, * read Java Concurrency In Practice right away! */ }

Es gratis y de código abierto bajo la licencia de Apache 2.0.