2010-05-17 18 views
5

A menudo lee sobre objetos inmutables que requieren que los campos finales sean inmutables en Java. ¿Es este el caso, o es simplemente suficiente para no tener mutabilidad pública y no mutar realmente el estado?¿Los campos deben ser explícitamente definitivos para tener un objeto inmutable "adecuado"?

Por ejemplo, si tiene un objeto inmutable construido por el patrón del constructor, puede hacerlo haciendo que el constructor asigne los campos individuales a medida que se construye o haciendo que el constructor contenga los campos y finalmente devuelva el objeto inmutable pasando los valores a su constructor (privado).

Tener los campos finales tiene la ventaja obvia de evitar errores de implementación (como permitir que el código retenga una referencia al constructor y "construir" el objeto varias veces mientras muta un objeto existente), pero tener el almacén de compilación sus datos dentro del objeto tal como está construido parecerían ser DRYer.

Así que la pregunta es: suponiendo que el generador no pierde el objeto temprano y se detiene a sí mismo de modificar el objeto una vez construido (por ejemplo, estableciendo su referencia al objeto como nulo) ¿hay algo ganado (como seguridad de hilo mejorado)) en la "inmutabilidad" del objeto si los campos del objeto se hicieron finales en su lugar?

+1

Si no me equivoco * final * también puede ayudar a prevenir contra los "ataques de reflexión" donde un programador deshonesto usaría la reflexión para acceder a los campos de su clase y modificar su contenido. Hubo un famoso ejemplo que pervirtió completamente la clase * String * y demostró que en muchos casos * String * en realidad podría ser modificado (si no es debido a no-finalness por cierto, pero debido al hecho de que una vez el subyacente * char [] * se accedió si se había "terminado el juego" porque no se puede forzar la inmutabilidad en el contenido de una matriz ... Pero ese no es mi punto: mi punto es que la reflexión puede ayudar a hacer cosas realmente desagradables). – TacticalCoder

+1

@user, frente a un reflejo como ese, no se puede establecer la inmutabilidad. Sin embargo, Security Manager se trata de hacer que ejecutar código de terceros sea viable. – Yishai

+0

espera ... Si tengo una clase * final * con una * int * final única, ¿puede modificarla utilizando la reflexión? Mi punto era precisamente que * sin * usar * final * no puedes evitar los ataques de reflexión (a menos que tengas un * Security Manager * en su lugar, pero casi nunca es el caso, por eso escribí * "en muchos casos puede ser modificado"*...). Sin embargo, no estoy seguro de que pueda usar la reflexión para modificar una * int final ... ... Si no puede, mi comentario es bastante sobre el tema con su pregunta:) (Hice +1 su pregunta ayer por cierto:) – TacticalCoder

Respuesta

6

Sí, obtiene "seguridad de subprocesos" desde final campos. Es decir, se garantiza que el valor asignado a un campo final durante la construcción sea visible para todos los hilos. La otra alternativa para la seguridad del hilo es declarar los campos volatile, pero luego se incurre en una sobrecarga elevada con cada lectura & hellip; y confundir a cualquiera que observe su clase y se pregunte por qué los campos de esta clase "inmutable" están marcados como "volátiles".

Marcar los campos final es el más correcto técnicamente, y transmite su intención más claramente. Desafortunadamente, hace que el patrón de construcción sea muy engorroso. Creo que debería ser posible crear un procesador de anotaciones para sintetizar un generador para una clase inmutable, al igual que Project Lombok hace con los setters y getters. El verdadero trabajo sería el soporte IDE necesario para que pueda codificar contra los constructores que realmente no existen.

+0

¿Por qué es necesario 'volátil'? – curiousguy

+0

@curiousguy: 'volátil' se asegurará de que el valor escrito por un hilo sea visible para otro. Sin él, un lector, por ejemplo, podría no borrar un valor en caché en un registro y hacer una lectura en la memoria principal. – erickson

+0

Ver mi comentario-respuesta. – curiousguy

2

Un Objeto ciertamente puede tener campos privados mutables y aún funcionar como un objeto inmutable. Todo lo que importa para cumplir con el contrato de inmutabilidad es que el objeto parece inmutable desde el exterior. Un objeto con campos privados no finales pero sin setters, por ejemplo, cumpliría este requisito.

De hecho, si su encapsulación es correcta, puede realmente mutar el estado interno y seguir funcionando correctamente como un objeto "inmutable". Un ejemplo podría ser algún tipo de evaluación o almacenamiento en caché de estructuras de datos.

Clojure, por ejemplo, hace esto en su implementación interna de secuencias perezosas, estos objetos se comportan como si fueran inmutables, pero solo calculan y almacenan valores futuros cuando se solicitan directamente. Cualquier solicitud posterior recupera el valor almacenado.

Sin embargo, me gustaría añadir como advertencia que la cantidad de lugares donde realmente querría mutar el interior de un objeto inmutable es probablemente bastante raro. Si tiene dudas, haga que sean definitivas.

+1

"Un objeto con campos privados no finales pero sin setters, por ejemplo, cumpliría este requisito" - Esto por sí solo no garantiza la inmutabilidad. Los Getters pueden devolver referencias de miembros de datos, y estos pueden ser modificados por la persona que llama. –

+0

@Eyal: verdadero, aunque las referencias mismas serían inmutables. Si construyes el objeto con miembros inmutables, la inmutabilidad se mantendría hasta abajo. Alternativamente, crear una composición inmutable o una colección de objetos mutables tiene sentido en algunos contextos. – mikera

+0

Debería añadir: diferentes personas tienen definiciones ligeramente diferentes de inmutabilidad ... la mía es "inmutabilidad desde la perspectiva del observador", que creo que es la más útil, pero también he escuchado que la gente define la inmutabilidad como absolutamente ningún cambio permitido. – mikera

0

Creo que solo necesitaría considerar el entorno en el que se ejecuta y decidir si los marcos que utilizan la reflexión para manipular objetos son un peligro.

Uno podría fácilmente cocinar un escenario extraño donde un objeto supuestamente inmutable es golpeado a través de un ataque de inyección POST debido a un marco de enlace web que está configurado para usar reflexión en lugar de establecedores de frijol.

0

Definitivamente puede tener un objeto inmutable con campos no finales.

Por ejemplo, vea la implementación de java 1.6 de java.lang.String.

+0

@ user988052 ¿No rompe la integridad de clase y la seguridad del programa? – curiousguy

+0

@TacticalCoder - Incluso los campos finales se pueden modificar mediante reflexión. Entonces, el argumento de que puedes tener "cadenas no inmutables" es simplemente falso, a menos que también estuvieras dispuesto a decir que es imposible tener un objeto inmutable en Java. La clase String, por diseño, es inmutable. La reflexión da un paso al costado del diseño de una clase y realmente no es relevante en el contexto de la pregunta original, ni la respuesta de este póster. – Scrubbie

+0

@TacticalCoder. Por qué -1. ¿Crees que String es mutable? ¿Has visto su implementación en 1.6? Al igual que con cualquier filosofía de diseño, usted confía en sus subcontratistas y en usted mismo para operar el equipo y las subpartes de acuerdo con las especificaciones de operación. Si comienza a operar fuera de estas especificaciones (como el uso de la reflexión sobre objetos inmutables), el resultado final puede ser similar a Chernobyl -> http://en.wikipedia.org/wiki/Chernobyl_disaster. –

0

Comentario: @erickson

Al igual que:

 
class X { volatile int i, j; } 
X y; 

// thread A: 
X x = new X; 
x.i = 1; 
x.j = 2; 
y = x; 

// thread B: 
if (y != null) { 
    a = y.i; 
    b = y.j; 
} 

?

+2

El hilo B no garantiza que 'y' se haya asignado; aunque 'y' se puede asignar" antes "de que B lo lea, de acuerdo con un reloj en la pared, no hay barrera de memoria, por lo que B podría leer un valor obsoleto de' null' de 'y'. Aquí, si eliminas 'volátil' de' i' y 'j', y lo pones en' y', B vería valores "actuales" para 'i',' j', y 'y'. Esto se debe a que la escritura en un campo volátil vacía las escrituras en cualquier campo de la memoria principal, y la lectura de una variable volátil actualiza los valores almacenados en caché de la memoria principal. – erickson

Cuestiones relacionadas