2012-06-08 142 views
16

Esta pregunta se trata de buenas prácticas de programación y evitar posibles agujeros.
Leo el Java efectivo de Joshua Bloch y esto es lo que me pregunto:
¿Por qué debería considerar hacer copias defensivas en métodos getter en mi clase inmutable sin mutadores?
Y segundo: ¿por qué debería hacer que mis campos final además de privado? ¿Se trata solo de rendimiento (no de seguridad)?¿Por qué hacer copias defensivas en getters dentro de clases inmutables?

+9

había pensado que Josuah explica él mismo en el libro, no lo hace ¿él? –

+1

Hacer campos 'final' tiene algunos beneficios de rendimiento, pero en realidad, el beneficio es para usted al hacer que sea más difícil modificar accidentalmente el campo y romper la inmutabilidad. –

+0

La inmutabilidad en el sentido general es diferente de la inmutabilidad en el sentido JMM. Las promesas de la JMM que tienen que ver con objetos inmutables (como nunca ser visto en un estado incoherente, incluso sin sincronización) solo se aplican a las cosas marcadas como 'final'. – Kevin

Respuesta

38

Creo que este es el caso que justifica esta declaraciones:

public class Immutable { 

    private final String name; 

    private Date dateOfBirth; 

    public Immutable(String name, Date dateOfBirth) { 
     this.name = name; 
     this.dateOfBirth = dateOfBirth; 
    } 

    public String getName() { 
     return name; 
    } 

    public Date getDateOfBirth() { 
     return dateOfBirth; 
    } 

} 

getName() está muy bien, ya que vuelve objeto inmutable también. Sin embargo el método getDateOfBirth() puede romper la inmutabilidad debido a que el código de cliente puede modificar vuelto objeto, por lo tanto, modificar el objeto Immutable así:

Immutable imm = new Immutable("John", new Date()); 

imm.getName(); //safe 
Date dateOfBirth = imm.getDateOfBirth(); 
//hundreds of lines later 
dateOfBirth.setTime(0); //we just modified `imm` object 

Es seguro regresar objetos inmutables y primitivas (ya que son devueltos por valor). Sin embargo es necesario hacer copias de defensa de los objetos mutables, como Date:

public Date getDateOfBirth() { 
    return new Date(dateOfBirth.getTime()); 
} 

y envolver en colecciones vistas inmutables (si es que son mutables), por ejemplo, ver Collections.unmodifiableList():

public List<Integer> getSomeIds() { 
    return Collections.unmodifiableList(someIds); 
} 
+1

Gracias por la respuesta. 'public Date getDateOfBirth() { return new Date (dateOfBirth.getTime()); } ' ¿Es esta la manera correcta de prevenir esto? – iozee

+0

@iozee: correcto, añadiré esto a mi respuesta –

+0

Sin embargo, tendría cuidado al cambiar el código existente a este: el nuevo código no será nulo seguro. Esto significa que si alguno de estos valores de fecha podría ser "nulo" de manera realista, tendrá que agregar algunos controles en su getter. – JoshC13

1

Mientras que la clase envolvente puede ser inmutable, las referencias devueltas por sus métodos de acceso podría ser mutable, lo que permite a la persona que llama modificar el estado transitiva de lo inmutable. Ejemplo:

public class MyImmutable 
{ 
    private final StringBuilder foo = new StringBuilder("bar"); 

    public StringBuilder getFoo() 
    { 
    return foo; // BAD! 
    } 
} 

Debe utilizar privada para la encapsulación (clases de prevenir dependiendo de su clase detalles de implementación). Debería usar final para asegurarse de no modificar el campo por error si no está destinado a ser modificado (y sí, podría ayudar al rendimiento).

0

¿Por qué debería considerar hacer copias defensivas en métodos getter en mi clase inmutable sin mutadores en ella?

Esto solo es útil si los objetos devueltos no son ya inmutables.

Profundo la inmutabilidad de este tipo es útil para evitar cambios en cualquier objeto retenido por la clase inmutable.

Considera que tienes un Caché de objetos. Cada vez que se recupera un objeto del caché y se altera, se corre el riesgo de modificar también el valor en el caché.

¿Por qué debería hacer mis campos finales además de privados?

Simplemente para ayudarlo a lograr la inmutabilidad y evitar que los valores se alteren de forma accidental o deliberada una vez configurados (por ejemplo, mediante subclases).

0

1 Si no hace una copia defensiva, puede dejar que su objeto sea entregado, una vez entregado, no más su clase es inmutable, la persona que llama es libre de cambiar el objeto.

public class Immutable { 
    private List<Object> something; 

    public List<Object> getSomething(){ 
     return something; // everything goes for a toss, once caller has this, it can be changed 
    } 
} 

2 Si el campo es privado y no definitivo que significa que puede reinicializar el campo, pero si el campo es definitivo que se inicializará sola vez y no varias veces y lograr la inmutabilidad.

+0

Ok, pero incluso si es final, puedo cambiar lo que quiero en él (llamar a cualquier método). Por lo tanto, creo que no hacer el campo _private_ ni _final_ puede garantizar la inmutabilidad del objeto al que se hace referencia. – iozee

1

Debe hacer una copia defensiva solo si el objeto que está devolviendo en su captador es mutable, porque de lo contrario el cliente podría cambiar el estado de su objeto.

En cuanto a la pregunta final, no es estrictamente necesario hacer que los campos sean definitivos, sino que los convierte en una concesión final que no se puede modificar una vez que se crea el objeto.

De hecho, si necesita modificar algunos campos de su objeto después de haber sido creado, también está bien, pero debe asegurarse de que el código del cliente no pueda distinguir que el estado del objeto ha sido alterado. Lo que tienes que hacer inmutable es el estado externo visible del objeto, no el estado interno.

E.g. la clase String no calcula su código hash en la creación, lo calcula la primera vez que lo necesita y lo almacena en caché en un campo privado mutable.

Estoy asumiendo que su clase se declara definitiva o sólo constructores privados, de lo contrario subclases de que podrían alterar sus campos no finales de maneras impredecibles ...

un código de ejemplo para aclarar:

public final class Question {  //final class to assure that inheritor could not 
            // make it mutable 

    private int textLenCache = -1;  //we alter it after creation, only if needed 
    private final String text; 

    private Date createdOn; 

    public Immutable(String text, Date createdOn) { 
     Date copy=new Date(createdOn.getTime()) //defensive copy on object creation 

     //Ensure your class invariants, e.g. both params must be non null 
     //note that you must check on defensive copied object, otherwise client 
     //could alter them after you check. 
     if (text==null) throw new IllegalArgumentException("text"); 
     if (copy==null) throw new IllegalArgumentException("createdOn"); 

     this.text= text; //no need to do defensive copy an immutable object 
     this.createdOn= copy; 
    } 

    public int getTextLen() { 
     if (textLenCache == -1) 
      textLenCache=text.length(); //client can't see our changed state, 
              //this is fine and your class is still 
              //immutable 
     return textLenCache;  
    } 

    public Date getCreatedOn() { 
     return new Date(createdOn.getTime());   //Date is mutable, so defend! 
    } 

} 

EDITAR:

se necesita la copia defensiva en los parámetros del constructor mutables por dos razones:

  1. código del cliente podría cambiar el estado del parámetro después de que se crea el objeto. Debe hacer una copia del parámetro para evitar esta posibilidad. P.ej. pensar si String objeto constructor String(char[] value) había utilizado la matriz de caracteres que proporciona sin copiarlo: podrá alterar el contenido de la cadena mediante la modificación de la matriz de caracteres que proporciona en el constructor.

  2. desea asegurarse de que el estado del objeto mutable no cambie entre el momento en que comprueba la restricción y la hora en que lo copia en su campo. Por este motivo, siempre debe verificar la restricción en su copia local del parámetro.

+0

Gracias por el comentario. ¿Podría explicar por qué debería hacer copias defensivas en el constructor? ¿Por qué no puedo simplemente verificar que el objeto Date sea igual a null? – iozee

+0

Sí, agregué una edición en mi respuesta. –

+0

Eso es una buena adición al tema: debemos evitar la mutabilidad de nuestro objeto al evitar la mutabilidad de los parámetros del constructor. ¡Gracias! – iozee

3

Las referencias a objetos en Java son promiscuas; si una referencia de objeto se pasa a un método, ese método no tendrá forma de saber quién más podría tener una referencia a ese mismo objeto, o lo mismo que intentarían hacer con él. Del mismo modo, si un método devuelve una referencia de objeto, no hay forma de saber qué podría hacer el destinatario con él. En consecuencia, si un tipo permite a cualquier persona con una referencia mutarlo, la única forma en que una entidad que tenga una referencia a tal objeto puede garantizar que no se mutará es mantener una referencia a un objeto privado, que nunca ha tenido , y nunca conseguirá, una referencia a.

Una alternativa al estilo de copiado defensivo que se muestra aquí (construir una nueva instancia de objeto cada vez que se solicita información) es hacer que un método de recuperación de información acepte un objeto mutable y rellene ese objeto con la información recuperada. Este enfoque requiere que el código que está solicitando la información construya un objeto (probablemente en blanco) para aceptar la información antes de llamar al método para recuperarla, pero si uno lo hará, p. utilice un bucle para recuperar, examinar brevemente y descartar 100 piezas de información, puede ser más rápido construir un objeto que pueda reutilizarse cada vez que pase, que construir 100 objetos nuevos, cada uno de los cuales se usará solo brevemente.

+0

Gracias por el comentario. ¿Es como pasar algún objeto contenedor al método y devolverlo con los datos requeridos? Sería genial si me dieras un pequeño ejemplo, solo para entenderlo. – iozee

+0

@iozee: Sí. La persona que llama proporciona el contenedor en el que se supone que irán los datos, y la función llamada lo copia allí.Algo que me gusta de este enfoque es que deja en claro que los cambios realizados en el contenedor de la persona que llama después de que el método regrese no afectarán la fuente de datos subyacente. Por el contrario, si la persona que llama devuelve un objeto, no quedará claro qué efecto tendrán los cambios futuros en ese objeto en nada. – supercat

1

he modificado ligeramente respuesta Tomasz Nurkiewicz, para demostrar por qué toma final de fechaDeNacimiento no defiende que sea cambiado por una clase de cliente:

public class Immutable { 

     private final String name; 
     private final Date dateOfBirth; 

     public Immutable(String name, Date dateOfBirth) { 
      this.name = name; 
      this.dateOfBirth = dateOfBirth; 
     } 

     public String getName() { return name; } 

     public Date getDateOfBirth() { return dateOfBirth; } 

     public static void main(String[] args) { 

      //create an object 
      Immutable imm = new Immutable("John", new Date()); 

      System.out.println(imm.getName() + " " + imm.getDateOfBirth()); 

      //try and modify object's intenal 
      String name = imm.getName(); //safe because Integer is immutable 
      name = "George";    //has no effect on imm 

      Date dateOfBirth = imm.getDateOfBirth(); 
      dateOfBirth.setTime(0); //we just modified `imm` object 

      System.out.println(imm.getName() + " " + imm.getDateOfBirth()); 
     } 
    } 

    **Output:** 
    John Wed Jan 13 11:23:49 IST 2016 
    John Thu Jan 01 02:00:00 IST 1970 
Cuestiones relacionadas