web-dev-qa-db-fra.com

Objet immuable avec la variable membre ArrayList - pourquoi cette variable peut-elle être modifiée?

J'ai une classe avec différentes variables de membre. Il existe un constructeur et des méthodes getter, mais pas de méthode setter. En fait, cet objet devrait être immuable.

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

Maintenant, j'ai remarqué ce qui suit: quand j'obtiens la liste de variables avec une méthode getter, je peux ajouter de nouvelles valeurs et ainsi de suite - je peux changer la ArrayList. Lorsque j'appelle la prochaine fois get() pour cette variable, la variable ArrayList est renvoyée. Comment se peut-il? Je n'ai pas encore réglé, j'ai juste travaillé dessus! Avec String, ce comportement n'est pas possible. Alors, quelle est la différence ici?

18
nano7

Le fait que la référence à la liste soit immuable ne signifie pas que la liste à laquelle {il fait référence} _ est immuable.

Même si list était faite final ceci serait autorisé

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

mais ceci serait non autorisé:

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

Afin de rendre la liste immuable (éviter aussi la première ligne), je vous suggère d'utiliser Collections.unmodifiableList :

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

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

(Notez que cela crée une vue non modifiable de la liste. Si quelqu'un conserve la référence d'origine, la liste peut toujours être modifiée par ce biais.)


Avec une chaîne, ce problème n'est pas possible. Alors, quelle est la différence ici?

En effet, une String est déjà immuable (non modifiable), tout comme la liste le serait si vous la transformiez en liste non modifiable.

Comparaison:

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

Vous retournez une référence à la list. Et list n'est pas immuable.


Si vous ne souhaitez pas que votre liste soit modifiée, renvoyez-en une copie:

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

Ou vous pouvez retourner une liste non modifiable :

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

La clé est de comprendre que vous ne modifiez pas le string - vous modifiez la chaîne faisant référence à la liste contient .

En d'autres termes: si je prends un dollar de votre portefeuille et le remplace par un centime, je n'ai changé ni le dollar ni le centime - je viens de modifier le contenu de votre portefeuille.

Si vous souhaitez une vue en lecture seule dans la liste, consultez Collections.unmodifiableList. Cela n'empêchera pas la liste qu'il s'agisse de wrapping de changer de cours, mais empêchera toute personne disposant uniquement d'une référence à la liste non modifiable de modifier le contenu.

Pour une liste véritablement immuable, regardez Guava 's ImmutableList class.

3
Jon Skeet

Comme le disent les autres réponses, l’objet que vous retournez des accesseurs est toujours modifiable.

Vous pouvez activer cette liste en la décorant à l'aide de la classe Collections:

 list = Collections.unmodifiableList(list);

Si vous le retournez aux clients, ils ne pourront pas ajouter ou supprimer d'éléments. Cependant, ils peuvent toujours obtenir des éléments de la liste - vous devez donc vous assurer qu'ils sont également immuables, si c'est ce que vous recherchez!

2
planetjones

Collections.unmodifiableList () rend la liste non autorisable. Ce qui crée à nouveau une nouvelle liste de tableaux finale et remplace les méthodes add, remove, addall et clear pour déclencher une exception non prise en charge. C'est un hack de la classe Collections. Mais au moment de la compilation, cela ne vous empêche pas d'ajouter et de supprimer des éléments. Je préférerais cloner cette liste. Ce qui peut m'aider à garder mon objet existant immuable et ne me coûte pas de créer une nouvelle liste. Lire la différence entre le clonage et le nouvel opérateur ( http://www.javatpoint.com/object-cloning ). Aussi aidera à écraser mon code au moment de l'exécution. 

2
Pratik Adhau

Pour obtenir une liste vraiment immuable, vous devrez faire des copies complètes du contenu de la liste. UnmodifiableList rendrait seulement la liste de références quelque peu immuable. Maintenant, faire une copie complète de la liste ou du tableau sera difficile pour la mémoire avec la taille croissante. Vous pouvez utiliser la sérialisation/désérialisation et stocker la copie complète de array/list dans un fichier temporaire. Le passeur ne serait pas disponible, le membre visible devant être immuable. Le getter sérialise la variable membre dans un fichier puis le désialise pour obtenir une copie complète. La séraialisation a la nature innée d'aller dans les profondeurs d'un arbre d'objets. Cela garantirait une immuabilité complète à un coût de performances cependant.

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
Rajeev Ranjan

Par conséquent, vous ne devez pas fournir de méthode de lecture pour la liste si vous souhaitez éviter de la modifier. 

Ses objets restent toujours intacts puisque vous n'aviez pas de setters. Mais ce que vous faites est de supprimer/ajouter de nouveaux objets/différents et c’est très bien.

0
Boro

La référence à la liste est immuable, mais pas la liste. Si vous voulez que la liste elle-même soit immuable, envisagez d'utiliser ImmutableList

0
simon