que - ¿Por qué uno declararía una clase inmutable final en Java?
porque los string son inmutables (10)
Al contrario de lo que muchas personas creen, no es necesario hacer una clase inmutable final
.
El argumento estándar para hacer final
clases inmutables es que si no haces esto, entonces las subclases pueden agregar mutabilidad, violando así el contrato de la superclase. Los clientes de la clase asumirán la inmutabilidad, pero se sorprenderán cuando algo se mude debajo de ellos.
Si lleva este argumento a su extremo lógico, todos los métodos deberían hacerse final
, ya que de lo contrario una subclase podría anular un método de una manera que no se ajuste al contrato de su superclase. Es interesante que la mayoría de los programadores de Java ve esto como algo ridículo, pero de alguna manera están de acuerdo con la idea de que las clases inmutables deberían ser final
. Sospecho que tiene algo que ver con los programadores de Java, en general, que no se sienten del todo cómodos con la noción de inmutabilidad, y tal vez con algún tipo de pensamiento difuso relacionado con los múltiples significados de la palabra clave final
en Java.
El compilador no puede o debe hacer cumplir el contrato de su superclase conforme al contrato. El compilador puede hacer cumplir ciertos aspectos de su contrato (por ejemplo, un conjunto mínimo de métodos y sus firmas de tipo), pero hay muchas partes de contratos típicos que el compilador no puede aplicar.
La inmutabilidad es parte del contrato de una clase. Es un poco diferente de algunas de las cosas a las que las personas están más acostumbradas, porque dice lo que la clase (y todas las subclases) no pueden hacer, mientras que la mayoría de los programadores de Java (y generalmente OOP) tienden a pensar en contratos relacionados con lo que una clase puede hacer, no lo que no puede hacer.
La inmutabilidad también afecta más que a un solo método, ya que afecta a toda la instancia, pero esto no es muy diferente de la forma equals
y hashCode
en Java. Esos dos métodos tienen un contrato específico establecido en Object
. Este contrato establece muy cuidadosamente las cosas que estos métodos no pueden hacer. Este contrato se hace más específico en subclases. Es muy fácil anular equals
o hashCode
de una manera que infringe el contrato. De hecho, si anula solo uno de estos dos métodos sin el otro, es probable que esté violando el contrato. Entonces, ¿ equals
y hashCode
han sido declarados final
en Object
para evitar esto? Creo que la mayoría argumentaría que no deberían. Del mismo modo, no es necesario hacer final
clases inmutables.
Dicho eso, la mayoría de tus clases, inmutables o no, probablemente deberían ser final
. Consulte el artículo 17 de la segunda edición de Java efectivo : "Diseñar y documentar para herencia o prohibirlo".
Entonces, una versión correcta de su paso 3 sería: "Haga que la clase sea definitiva o, cuando diseñe para la subclasificación, documente claramente que todas las subclases deben seguir siendo inmutables".
Leí que para hacer una clase inmutable en Java, debemos hacer lo siguiente,
- No proporcione ningún setters
- Marcar todos los campos como privados
- Haz la clase final
¿Por qué se requiere el paso 3? ¿Por qué debería marcar la final
la clase?
El diseño en sí mismo no tiene valor. El diseño siempre se usa para lograr un objetivo. ¿Cuál es el objetivo aquí? ¿Queremos reducir la cantidad de sorpresas en el código? ¿Queremos prevenir errores? ¿Estamos siguiendo ciegamente las reglas?
Además, el diseño siempre tiene un costo. Cada diseño que merece el nombre significa que tiene un conflicto de objetivos .
Con eso en mente, necesita encontrar respuestas a estas preguntas:
- ¿Cuántos errores obvios evitará esto?
- ¿Cuántos errores sutiles evitará esto?
- ¿Con qué frecuencia esto hará que otros códigos sean más complejos (= más propensos a errores)?
- ¿Esto hace que las pruebas sean más fáciles o más difíciles?
- ¿Qué tan buenos son los desarrolladores en tu proyecto? ¿Cuánta guía con un martillo necesitan?
Supongamos que tiene muchos desarrolladores junior en su equipo. Intentarán desesperadamente cualquier cosa estúpida solo porque aún no conocen buenas soluciones para sus problemas. Hacer que la clase sea definitiva podría evitar errores (buenos), pero también podría hacer que propongan soluciones "inteligentes" como copiar todas estas clases en las partes mutables en cualquier parte del código.
Por otro lado, será muy difícil hacer una clase final
después de que se use en todas partes, pero es fácil hacer una clase final
no final
más tarde si descubres que necesitas extenderla.
Si usa correctamente las interfaces, puede evitar el problema de "Necesito hacer que esto mutable" al usar siempre la interfaz y luego agregar una implementación mutable cuando surja la necesidad.
Conclusión: no hay una "mejor" solución para esta respuesta. Depende de qué precio esté dispuesto y qué tenga que pagar.
El significado por defecto de equals () es lo mismo que la igualdad referencial. Para tipos de datos inmutables, esto casi siempre es incorrecto. Por lo tanto, debe anular el método equals (), reemplazándolo con su propia implementación. link
Eso limita otras clases que extienden tu clase.
la clase final no puede ser extendida por otras clases.
Si una clase extiende la clase que desea que sea inmutable, puede cambiar el estado de la clase debido a los principios de herencia.
Solo aclare "puede cambiar". La subclase puede anular el comportamiento de las superclases al usar la anulación de método (como templatetypedef / Ted Hop answer)
No marques toda la clase final.
Hay razones válidas para permitir que una clase inmutable se extienda como se indica en algunas de las otras respuestas, por lo que marcar la clase como final no siempre es una buena idea.
Es mejor marcar sus propiedades como privadas y finales, y si desea proteger el "contrato", marque sus compradores como definitivos.
De esta forma, puedes permitir que la clase se extienda (sí, posiblemente incluso mediante una clase mutable), sin embargo, los aspectos inmutables de tu clase están protegidos. Las propiedades son privadas y no se puede acceder, los captadores de estas propiedades son definitivos y no pueden anularse.
Cualquier otro código que use una instancia de su clase inmutable podrá confiar en los aspectos inmutables de su clase, incluso si la subclase que se pasa es mutable en otros aspectos. Por supuesto, dado que toma una instancia de su clase, ni siquiera sabría sobre estos otros aspectos.
Para crear una clase inmutable, no es obligatorio marcar la clase como final.
Déjame tomar uno de esos ejemplos de las clases de Java en sí. La clase "BigInteger" es inmutable pero no es definitiva.
En realidad, la inmutabilidad es un concepto según el cual el objeto creado no puede ser modificado.
Pensemos desde el punto de vista de JVM, desde el punto de vista JVM todos los hilos deben compartir la misma copia del objeto y está completamente construido antes de que cualquier hilo lo acceda y el estado del objeto no cambie después de su construcción.
La inmutabilidad significa que no hay forma de que cambie el estado del objeto una vez que se crea y esto se logra mediante tres reglas de pulgar que hacen que el compilador reconozca que la clase es inmutable y son las siguientes:
- todos los campos no privados deben ser finales
- asegúrese de que no haya ningún método en la clase que pueda cambiar los campos del objeto directa o indirectamente
- cualquier referencia de objeto definida en la clase no se puede modificar fuera de la clase
Para obtener más información, consulte la siguiente URL
http://javaunturnedtopics.blogspot.in/2016/07/can-we-create-immutable-class-without.html
Si no es final, cualquiera puede extender la clase y hacer lo que quiera, como proporcionar setters, sombrear sus variables privadas y, básicamente, hacerlo mutable.
Si no lo haces definitivo, puedo extenderlo y hacerlo no mutable.
public class Immutable {
privat final int val;
public Immutable(int val) {
this.val = val;
}
public int getVal() {
return val;
}
}
public class FakeImmutable extends Immutable {
privat int val2;
public FakeImmutable(int val) {
super(val);
}
public int getVal() {
return val2;
}
public void setVal(int val2) {
this.val2 = val2;
}
}
Ahora, puedo pasar FakeImmutable a cualquier clase que se espera que sea Inmutable, y no se comportará como el contrato esperado.
Si no marcas la final
la clase, podría hacer que tu clase aparentemente inmutable cambie de repente. Por ejemplo, considere este código:
public class Immutable {
private final int value;
public Immutable(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
Ahora, supongamos que hago lo siguiente:
public class Mutable extends Immutable {
private int realValue;
public Mutable(int value) {
super(value);
realValue = value;
}
public int getValue() {
return realValue;
}
public void setValue(int newValue) {
realValue = newValue;
}
public static void main(String[] arg){
Mutable obj = new Mutable(4);
Immutable immObj = (Immutable)obj;
System.out.println(immObj.getValue());
obj.setValue(8);
System.out.println(immObj.getValue());
}
}
Tenga en cuenta que en mi subclase Mutable
, he reemplazado el comportamiento de getValue
para leer un nuevo campo mutable declarado en mi subclase. Como resultado, su clase, que inicialmente parece inmutable, en realidad no es inmutable. Puedo pasar este objeto Mutable
donde se espera un objeto Immutable
, que podría hacer cosas muy malas para codificar asumiendo que el objeto es realmente inmutable. Marcar la clase base final
evita que esto suceda.
¡Espero que esto ayude!
Supongamos que la siguiente clase no fuera final
:
public class Foo {
private int mThing;
public Foo(int thing) {
mThing = thing;
}
public int doSomething() { /* doesn''t change mThing */ }
}
Es aparentemente inmutable porque incluso las subclases no pueden modificar mThing
. Sin embargo, una subclase puede ser mutable:
public class Bar extends Foo {
private int mValue;
public Bar(int thing, int value) {
super(thing);
mValue = value;
}
public int getValue() { return mValue; }
public void setValue(int value) { mValue = value; }
}
Ahora ya no se garantiza que un objeto que se puede asignar a una variable de tipo Foo
sea fusible. Esto puede causar problemas con cosas como hashing, igualdad, concurrencia, etc.