Los objetos inmutables son aquellos que no cambian su estado una vez que su constructor ha sido ejecutado. Un ejemplo de esto es la clase java.lang.String. La inmutabilidad es implementada como un patrón de diseño que el programador aplica dentro de la clase.
A continuación algunas características generales que usualmente están presentes en todos los objetos intencionalmente inmutables:
- La clase es instanciada y todas sus propiedades son fijadas a través de su(s) constructor(es). Como los argumentos son suministrados durante la creación, los métodos 'setter' en la clase son innecesarios.
- La clase es declarada
final. Esto impide a otros desarrolladores que extiendan la clase y la hagan mutable. - Las propiedades de la clase son
private. Esto impide que otros objetos modifiquen los atributos directamente. - No hay ningun código en la clase, a parte de los constructores, que modifiquen las propiedades del objeto. Cualquier método o evento para la clase debe tratar los atributos como de solo-lectura. De esta forma se garantiza que el estado final del objeto es determinado cuando se construye.
- Si existen métodos que realizan operaciones sobre las propiedades del objeto, el resultado de estas operaciones es otra instancia de la clase que contiene los datos modificados. Este es el caso de los métodos
trim()ytoUpperCase()de la clasejava.lang.String. - Si el propósito de la clase es lo suficientemente general, ella es usualmente empaquetada con una "pareja" que si es mutable. Este es el caso con las clases
java.lang.Stringyjava.lang.StringBuffer.
Pero, ¿para que sirven los objetos inmutables?, para tratar de responder a esto consideremos el siguiente ejemplo:
En la tercera línea de este método, el objeto key es agregado al hashmap con un nuevo objeto. En este punto, el hashmap contendrá un objeto String como "clave" y un objeto ArrayList como su "valor". En la cuarta línea, a key se le da un nuevo valor, "key2", de hecho, dado que key es instanciado a partir de la clase (inmutable) String, key es ahora un objeto distinto del que está almacendao en el hashmap, es decir dos objetos String existen en memoria. En la quinta línea, el valor "key1" (el tercer String creado) es usado para recuperar el ArrayList desde el hashmap. ¿Qué pasaría si String no fuera inmutable?, pues que en la cuarta línea, key tendría un nuevo valor, "key2", igual que la clave usada para referenciar al ArrayList en el hashmap. De hecho en este punto existiría un solo objeto String en memoria. Otro beneficio de los objetos inmutables es que son inherentemente 'thread safe'. 'Thread safe' significa que dos o más 'threads' no pueden hacer modificaciones al objeto en el mismo instante de tiempo. Usualmente, los métodos 'setter' de las clases deben ser declarados synchronized si se desea prevenir que esto ocurra.
Adicionalmente, los objetos inmutables no necesitan ser clonados cuando son pasados como argumentos. Si la función "llamada" intenta modificar un String, esta tendrá su propia copia del objeto con el resultado de la modificación, pero el argumento permanecerá intacto. Imagina el desastre (en tiempo, traza y depuración) si tuvieramos que clonar los strings cada vez que quisieramos pasarlos como argumentos. Por estas razones, los objetos inmutables son un concepto importante en el desarrollo orientado-a-objetos. Ellos son 'thread safe', son unas "claves" excelentes para los Map, y no necesitan ser clonados cuando son pasados como argumentos a otros métodos. Una forma de implementar la inmutabilidad es usando final. El 'keyword' final en Java puede ser aplicado a clases, atributos y métodos:
- Una clase
finalimpide que otras clases puedan extenderla. - Un método
finalno puede ser sobreescrito por las subclases (asumiendo que la clase no seafinal). - Un atributo
finalserá tratado como una constante que no podrá ser modificado.
Algunos veces los métodos son declarados final no solo para prevenir que sea sobreescrito, sino para que se ejecuten más rápido. Esto se debe al hecho que la máquina virtual no gasta ciclos de cpu ejecutando un algoritmo de llamada al método. En cambio, el compilador (dependiendo de la implementación) coloca una copia del del código 'inline' en el punto de la llamada. La mejora en la velocidad es más notable cuando los métodos son pequeños y usados frecuentemente. Los métodos 'accessors', por ejemplo, pueden benficiarse al ser declarados como final, ya que raramente necesitan ser sobreescritos. El uso del modificador final con los atributos ayuda a la implementación de la inmutabilidad. Veamos el siguiente ejemplo:
-
public class ObjectServer
-
{
-
public final int mServerPort;
-
-
public ObjectServer()
-
{
-
mServerName = "localhost";
-
mServerPort = 80;
-
}
-
-
{
-
mServerName = pServerName;
-
mServerPort = 80;
-
-
}
-
-
{
-
mServerName = pServerName;
-
mServerPort = pServerPort;
-
}
-
}
Los dos atributos final en esta clase no tienen valores iniciales, a estos atributos se les conoce como 'blank finals'. El compilador verifica que todos los atributos final tengan un valor asignado en el inicializador de la instancia o en los constructores. En este caso, hay tres constructores y el compilador asegurará que ambos atributos final tengan valores asignados en cada constructor. El resultado de esto es que los valores de los atributos final pueden ahora ser dinámicos en vez de constantes, es decir, cada objeto creado puede tener distintos valores mientras sigue siendo inmutable. Es posible hacer lo mismo con atributos static final colocando el código en un bloque static en vez de en el constructor. La siguiente clase ilustra esto:
-
public class StaticExample
-
{
-
public static final int PORT_NUMBER;
-
static
-
{
-
PORT_NUMBER = 21;
-
}
-
}
Desafortunadamente, las restricciones con el código static hacen que esta alternativa sea de poca utilidad para la mayoría de las aplicaciones. Por ejemplo, un objeto Applet no es capaz de leer sus parámetros y asignarlos a atributos static, porque el Applet debe tener una referencia a su contexto en el browser. Y esto no ocurre despues que se haya ejecutado el constructor, mucho despues de que el bloque static ha sido ejecutado, asi que la única manera de hacer esto es hacer el valor static y no-final. Si la inmutabilidad es un requerimiento, el desarrollador siempre puede hacer el atributo final y proveer un método de solo-lectura (tambien static).
Un aspecto del que hay que estar atento es que la declaración de un atributo de un objeto como final no impide que el atributo sea modificado. De hecho, el final solamente aplica a la referencia al objeto y no al objeto en sí mismo. El ejemplo que sigue ilustra esto, el primer método compilará, pero el segundo no:
-
public static void test(final FinalObject fo)
-
{
-
fo.setSomeValue(10);
-
}
-
-
public static void test2(final FinalObject fo)
-
{
-
// no se puede asignar un valor a la variable final 'fo'
-
fo = new FinalObject();
-
fo.setSomeValue(20);
-
}
Muy buen post, me gustó mucho como lo explicas.
Felicitaciones!
hello quiero un nick brabazo no qualquiera
Claro y sencillo. Excelente.