2012-09-06 15 views
40

leí que para hacer una class immutable en Java, debemos hacer lo siguiente,¿Por qué se declararía una clase inmutable final en Java?

  1. no proporcionan ningún set
  2. Marcar todos los campos como privada
  3. hacer que la clase final

¿Por qué se requiere el paso 3? ¿Por qué debería marcar la clase final?

+0

La clase 'java.math.BigInteger' es un ejemplo, sus valores son inmutables pero no es definitiva. –

+0

@Nandkumar Si tiene un 'BigInteger', no sabe si es inmutable o no. Es un diseño desordenado./'java.io.File' es un ejemplo más interesante. –

+1

No permita que las subclases anulen los métodos: la forma más sencilla de hacerlo es declarar la clase como definitiva. Un enfoque más sofisticado es hacer que el constructor sea privado y construir instancias en métodos de fábrica: desde https://docs.oracle.com/javase/tutorial/essential/concurrency/imstrat.html –

Respuesta

75

Si no marca la clase final, me es posible hacer repentinamente mutable su clase aparentemente inmutable. 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 yo haga 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()); 
    } 
} 

en cuenta que en mi Mutable subclase, hemos anulado el comportamiento de getValue a leer un nuevo, mutable campo 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, lo que podría hacer que Very Bad Things codifique asumiendo que el objeto es realmente inmutable. Marcar la clase base final evita que esto suceda.

Espero que esto ayude!

+4

Si lo hago Mutable m = new Mutable (4); m.setValue (5); Aquí, estoy jugando con objeto de clase Mutable, no objeto de clase Inmutable. Así que, todavía estoy confundido por qué la clase Inmutable no es inmutable – Anand

+8

@ anand- Imagine que tiene una función que toma un argumento 'Inmutable'. Puedo pasar un objeto 'Mutable' a esa función, ya que' Mutable extends Immutable'. Dentro de esa función, mientras piensas que tu objeto es inmutable, podría tener un hilo secundario que vaya y cambia el valor a medida que se ejecuta la función. También podría darle un objeto 'Mutable' que la función almacene, luego cambiar su valor externamente. En otras palabras, si su función asume que el valor es inmutable, podría romperse fácilmente, ya que podría darle un objeto mutable y cambiarlo más adelante. ¿Tiene sentido? – templatetypedef

+0

@ templatetypedef- puede sonar estúpido, pero en realidad todavía no lo tengo claro. Por ejemplo, tome un ejemplo que diga que tengo un método de diversión nula (Immutable i). Paso este método Objeto mutable, digamos m. Ahora, ¿cómo puedo cambiar el objeto ... ¿Puede explicarlo con referencia al ejemplo del código o si puede dar otro ejemplo, estoy de acuerdo con eso también ... – Anand

4

Eso limita otras clases que amplían su clase.

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 que "puede cambiar". La subclase puede anular el comportamiento de las superclases al usar el método overriding (como templatetypedef/Ted Hop answer)

+2

Eso es cierto, pero ¿por qué es necesario aquí? – templatetypedef

+1

sí, ¿por qué es necesario? – Anand

+0

@templatetypedef: Eres demasiado rápido. Estoy editando mi respuesta con puntos de apoyo. – kosa

5

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 mudable.

+0

Aunque tiene razón, no está claro por qué el hecho de que puede agregar comportamiento mutable necesariamente rompería las cosas si los campos de la clase base son todos privados y finales. El peligro real es que la subclase podría convertir un objeto previamente inmutable en un objeto mutable anulando el comportamiento. – templatetypedef

1

Si no lo hace 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, me puede pasar FakeImmutable a cualquier clase que espera inmutable, y no se comportará como el contrato esperada.

+2

Creo que esto es más o menos idéntico a mi respuesta. – templatetypedef

+0

Sí, comprobado a mitad de la publicación, no hay respuestas nuevas. Y luego cuando se publicaron, allí estaban los tuyos, 1 minuto antes que yo. Al menos no usamos los mismos nombres para todo. –

+0

Una corrección: en la clase FakeImmutable, el nombre del constructor debe ser FakeImmutable NO inmutable –

0

Supongamos la siguiente clase no final fueron:

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 un objeto que se puede asignar a una variable de tipo Foo ya no se garantiza que sea mmutable. Esto puede causar problemas con cosas como hashing, igualdad, concurrencia, etc.

19

Contrariamente a lo que mucha gente cree, hacer una clase inmutable final es no es necesario.

El argumento estándar para hacer que las clases inmutables final sea que si no lo hace, 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 se toma este argumento a su extremo lógico, entonces todos métodos deben hacerse final, ya que de lo contrario una subclase podría reemplazar un método de una manera que no sea conforme con el contrato de su superclase. Es interesante que la mayoría de los programadores de Java ven 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 que, en general, 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. El compilador puede hacer cumplir ciertos aspectos de su contrato (p. Ej .: 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 forma 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 que la clase (y todas las subclases) no pueden hacer, mientras que la mayoría de los programadores Java (y generalmente OOP) tienden a pensar en contratos como relacionado con lo que una clase puede hacer, no lo que no puede hacer.

inmutabilidad también afecta a algo más que un solo método — que afecta a toda la instancia — pero esto no es realmente muy diferente a la forma en equals y hashCode en el trabajo de 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, ¿deberían equals y hashCode haberse declarado final en Object para evitar esto? Creo que la mayoría argumentaría que no deberían. Del mismo modo, no es necesario hacer clases inmutables final.

Dicho esto, la mayoría de sus clases, inmutables o no, probablemente debería ser final. Ver Effective Java Second Edition Elemento 17: "Diseñar y documentar para herencia o prohibirlo".

Así que una versión correcta de su paso 3 sería: "Haga que la clase sea definitiva o, cuando diseñe para la creación de subclases, documente claramente que todas las subclases deben seguir siendo inmutables".

+1

Vale la pena señalar que Java "espera", pero no aplica, que dados dos objetos 'X' y' Y', el valor de 'X.equals (Y)' será inmutable (siempre que 'X' y' Y' continuará refiriéndose a los mismos objetos). Tiene expectativas similares con respecto a los códigos hash. Está claro que nadie debería esperar que el compilador aplique la inmutabilidad de las relaciones de equivalencia y los códigos hash (ya que simplemente no puede). No veo ninguna razón por la que las personas deban esperar que se aplique para otros aspectos de un tipo. – supercat

+1

Además, hay muchos casos en los que puede ser útil tener un tipo abstracto cuyo contrato especifique la inmutabilidad. Por ejemplo, uno puede tener un tipo ImmutableMatrix abstracto que, dado un par de coordenadas, devuelve un 'doble'. Uno podría derivar una 'GeneralImmutableMatrix' que utiliza una matriz como una tienda de respaldo, pero también podría tener, p. 'ImmutableDiagonalMatrix' que simplemente almacena un conjunto de elementos a lo largo de la diagonal (leyendo el elemento X, Y produciría Arr [X] si X == y, de lo contrario, cero). – supercat

+1

Me gusta esta explicación lo mejor. Siempre hacer una clase inmutable 'final' restringe su utilidad, especialmente cuando está diseñando una API destinada a ser extendida. En aras de la seguridad del hilo, tiene sentido hacer que su clase sea lo más inmutable posible, pero mantenerla extensible. Puede hacer que los campos estén 'protegidos' en lugar de 'privados'. Luego, establezca explícitamente un contrato (en la documentación) sobre las subclases que se adhieren a las garantías de inmutabilidad y seguridad de las hebras. – curioustechizen

9

No marque 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 manera puede permitir que la clase se amplíe (sí, posiblemente incluso mediante una clase mutable), sin embargo, los aspectos inmutables de su 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.

+0

Prefiero encapsular todos los aspectos inmutables en una clase separada y usarla por medio de la composición. Sería más fácil razonar que mezclar el estado mutable e inmutable en una sola unidad de código. – toniedzwiedz

+0

Totalmente de acuerdo. La composición sería una muy buena manera de lograr esto, siempre que tu clase mutable contenga tu clase inmutable. Si su estructura es al revés, sería una buena solución parcial. –

1

Para crear clases inmutables 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 .

inmutabilidad significa que no hay manera yo cambiar el estado del objeto una vez que se crea y esto se logra mediante tres reglas básicas que hace que el compilador de reconocer que la clase es inmutable y que son los siguientes: -

  • todos los campos privados no deben ser definitiva
  • asegúrese de que no hay un método en la clase que puede cambiar los campos del objeto, ya sea directa o indirectamente
  • ninguna referencia de objeto definido en la clase no se puede modificar desde el exterior 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

0

Diseño por sí mismo no tiene ningún 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. Every design that deserves the name means you have a conflict of goals.

Con esto en mente, es necesario encontrar respuestas a estas preguntas:

  1. ¿Cuántos errores se obvia este prevenir?
  2. ¿Cuántos errores sutiles evitará esto?
  3. ¿Con qué frecuencia esto hará que otros códigos sean más complejos (= más propensos a errores)?
  4. ¿Esto hace que las pruebas sean más fáciles o más difíciles?
  5. ¿Qué tan buenos son los desarrolladores en su 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 otra parte, va a ser muy duro para hacer una clase final después de que se está utilizando en todas partes, pero es fácil de hacer una clase final no final más adelante si se entera de lo que necesita para extenderla.

Si utiliza correctamente las interfaces, puede evitar el problema de "Tengo que hacer esta mutable" utilizando siempre la interfaz y luego agregando una implementación mutable cuando sea necesario.

Conclusión: No hay una "mejor" solución para esta respuesta. Depende de qué precio esté dispuesto y qué tenga que pagar.

Cuestiones relacionadas