2011-05-26 14 views
14

Tengo una clase con varias variables miembro. Hay un constructor y hay métodos getter, pero no hay métodos setter. De hecho, este objeto debe ser inmutable.Objeto inmutable con la variable miembro ArrayList: ¿por qué se puede cambiar esta variable?

public class Example { 
    private ArrayList<String> list; 
} 

Ahora me di cuenta de lo siguiente: cuando llegue a la lista de variables con un captador-método, puedo añadir nuevos valores y así sucesivamente - Puedo cambiar el ArrayList. Cuando llamo la próxima vez get() para esta variable, se devuelve el ArrayList cambiado. ¿Cómo puede ser esto? No lo puse de nuevo, ¡solo trabajé en eso! Con un String, este comportamiento no es posible. Entonces, ¿cuál es la diferencia aquí?

Respuesta

35

El hecho de que la referencia a la lista es inmutable no significa que la lista se refiere a es inmutable.

Incluso si list se hizo final esto se le permitiría

// changing the object which list refers to 
example.getList().add("stuff"); 

pero esto no permitido:

// changing list 
example.list = new ArrayList<String>(); // assuming list is public 

Con el fin de hacer que la lista inmutable (prevenir también la primera línea), sugiero que use Collections.unmodifiableList:

public class Example { 
    final private ArrayList<String> list; 

    Example(ArrayList<String> listArg) { 
     list = Collections.unmodifiableList(listArg); 
    } 
} 

(Tenga en cuenta que esto crea una vista no modificable de la lista. Si alguien se aferra a la referencia original, a continuación, la lista todavía puede ser modificado a través de eso.)


con una cadena este comportamiento isnt posible. Entonces, ¿cuál es la diferencia aquí?

Eso se debe a que ya es String inmutable (no modificable) al igual que la lista sería si lo convirtió en un unmodifiableList.

Comparación:

   String data structure | List data structure 
      .-------------------------+------------------------------------. 
Immutable | String     | Collection.unmodifiableList(...) | 
-----------+-------------------------+------------------------------------| 
Mutable | StringBuffer   | ArrayList       | 
      '-------------------------+------------------------------------' 
+0

yo sepa 'Collections.unmodifiableList()' devuelve una envoltura inmutable para la lista dada. Si estoy en lo correcto, lo anterior no garantizará la inmutabilidad. Una clase puede crear una instancia de una lista, instanciar un ejemplo y aún puede modificar la lista que contiene el ejemplo modificando la lista original pasada al constructor.Aunque la respuesta puede ser suficiente para abordar las diferencias, es posible que no cumpla con los estrictos requisitos de "inmutabilidad". – Maurice

+2

Eso es correcto. Respuesta actualizada Puede hacer 'Collections.unmodifiableList (new ArrayList <> (listArg))' para asegurarse de que nadie contiene una referencia a la lista mutable subyacente y así evitar la mutabilidad. – aioobe

15

Usted está regresando una referencia al list. Y list no es inmutable.


Si no quiere que su lista para ser cambiada devolver una copia del mismo:

public List<String> get() { 
    return new ArrayList<String>(this.list); 
} 

O puede devolver un unmodifiable list:

public List<String> get() { 
    return Collections.unmodifiableList(this.list); 
} 
3

La clave es entender que usted no está cambiando la cadena de - que está cambiando qué cadena hace referencia a la lista contiene.

Para decirlo de otra manera: si saco un dólar de tu billetera y lo reemplazo con un centavo, no he cambiado ni el dólar ni el centavo: acabo de cambiar el contenido de tu billetera.

Si desea una vista de solo lectura en la lista, consulte Collections.unmodifiableList. Eso no detendrá la lista de que es envolviendo cambiando, por supuesto, pero impedirá que cualquier persona que solo tenga una referencia a la lista no modificable modifique los contenidos.

Para una lista inmutable verdad, mira Guava 's ImmutableList clase.

0

Por lo tanto, no debe proporcionar un método getter para la lista si desea evitar que se modifique.

Sus objetos todavía permanecen intactos ya que no tenía setters. Pero lo que haces es eliminar/agregar objetos nuevos/diferentes y está bien.

2

Como las otras respuestas dicen que el Objeto que devuelve de los captadores sigue siendo mutable.

Se puede activar la lista inmutable por decorarlo utilizando la clase Colecciones:

list = Collections.unmodifiableList(list); 

Si devuelve esto a los clientes que no será capaz de añadir o eliminar elementos a la misma. Sin embargo, aún pueden sacar elementos de la lista, por lo que debes asegurarte de que también sean inmutables, ¡si eso es lo que buscas!

0

La referencia de lista es inmutable, pero no la lista. Si desea que la lista sea inmutable, considere utilizar ImmutableList

2

Collections.unmodifiableList() hace que la lista no sea modificable. Lo cual nuevamente crea una nueva lista final de arreglos y reemplaza los métodos add, remove, addall y clear para lanzar unsupportedoperationexception. Es un truco de la clase Colecciones. Pero en tiempo de compilación no te impide añadir y eliminar cosas. Prefiero ir con la clonación de esa lista. Lo cual puede ayudarme a mantener mi objeto existente inmutable y no me cuesta crear una nueva lista. Diferencia de lectura entre la clonación y el nuevo operador (http://www.javatpoint.com/object-cloning). También ayudará a bloquear mi código en tiempo de ejecución.

0

Para obtener una lista realmente inmutable, tendrá que hacer copias en profundidad de los contenidos de la lista. UnmodifiableList solo haría la lista de referencias algo inmutable. Ahora hacer una copia profunda de la lista o matriz será difícil para la memoria con el tamaño en crecimiento. Puede hacer uso de la serialización/deserialización y almacenar la copia profunda de la matriz/lista en un archivo temporal. El colocador no estaría disponible ya que el miembro varaible necesita ser inmutable. El getter serializaría la variable miembro en un archivo y luego la desializaría para obtener una copia profunda. La serialización tiene una naturaleza innata de ir a las profundidades de un árbol de objetos. Sin embargo, esto garantizaría la inmutabilidad completa a algún costo de rendimiento.

package com.home.immutable.serial; 

import java.io.File; 
import java.io.FileInputStream; 
import java.io.FileNotFoundException; 
import java.io.FileOutputStream; 
import java.io.IOException; 
import java.io.ObjectInputStream; 
import java.io.ObjectOutputStream; 

public final class ImmutableBySerial { 

    private final int num; 
    private final String str; 
    private final TestObjSerial[] arr; 

    ImmutableBySerial(int num, String str, TestObjSerial[] arr){ 
     this.num = num; 
     this.str = str; 
     this.arr = getDeepCloned(arr); 
    } 

    public int getNum(){ 
     return num; 
    } 

    public String getStr(){ 
     return str; 
    } 

    public TestObjSerial[] getArr(){ 
     return getDeepCloned(arr); 
    } 

    private TestObjSerial[] getDeepCloned(TestObjSerial[] arr){ 
     FileOutputStream fos = null; 
     ObjectOutputStream oos = null; 
     FileInputStream fis = null; 
     ObjectInputStream ois = null; 
     TestObjSerial[] clonedObj = null; 
     try { 
      fos = new FileOutputStream(new File("temp")); 
      oos = new ObjectOutputStream(fos); 
      oos.writeObject(arr); 
      fis = new FileInputStream(new File("temp")); 
      ois = new ObjectInputStream(fis); 
      clonedObj = (TestObjSerial[])ois.readObject(); 

     } catch (FileNotFoundException e) { 
      e.printStackTrace(); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } catch (ClassNotFoundException e) { 
      e.printStackTrace(); 
     } finally { 
      try { 
       oos.close(); 
       fos.close(); 
      } catch (IOException e) { 
       e.printStackTrace(); 
      } 
     } 
     return clonedObj; 
    } 
} 
0

Una otra solución es devolver copia del ArrayList no la lista de arreglo real como el código

import java.util.ArrayList; 
import java.util.List; 

public final class ImmutableExample { 

    private final List<String> strings = new ArrayList<String>(); 

    public ImmutableExample() { 
     strings.add("strin 1"); 
     strings.add("string 2"); 
    } 

    public List<String> getStrings() { 
     List<String> newStrings = new ArrayList<String>(); 
     strings.forEach(s -> newStrings.add(s)); 
     return newStrings; 
    } 

    public static void main(String[] args) { 
     // TODO Auto-generated method stub 
     ImmutableExample im = new ImmutableExample(); 
     System.out.println(im.getStrings()); 
     im.getStrings().add("string 3"); 
     System.out.println(im.getStrings()); 

    } 
} 
+0

Esto no agrega nueva información. La devolución de una copia ha sido explicada en esta respuesta: https://stackoverflow.com/a/6137267 e incluso usa una versión _much_ limpiadora de copiar la lista. – Tom

Cuestiones relacionadas