web-dev-qa-db-fra.com

Comment empêcher la modification d'un champ privé dans une classe?

Imaginez que j'ai cette classe:

public class Test
{
  private String[] arr = new String[]{"1","2"};    

  public String[] getArr() 
  {
    return arr;
  }
}

Maintenant, j'ai une autre classe qui utilise la classe ci-dessus:

Test test = new Test();
test.getArr()[0] ="some value!"; //!!!

Voilà donc le problème: j'ai accédé à un champ privé d'une classe de l'extérieur! Comment puis-je éviter cela? Je veux dire comment puis-je rendre ce tableau immuable? Cela signifie-t-il qu'avec chaque méthode getter, vous pouvez progresser pour accéder au domaine privé? (Je ne veux pas de bibliothèques telles que Guava. J'ai juste besoin de savoir comment faire cela).

164
Hossein

Vous devez retourner un copie de votre tableau.

public String[] getArr() {
  return arr == null ? null : Arrays.copyOf(arr, arr.length);
}
161
OldCurmudgeon

Si vous pouvez utiliser une liste au lieu d'un tableau, Collections fournit une liste non modifiable :

public List<String> getList() {
    return Collections.unmodifiableList(list);
}
375
sp00m

Le modificateur private protège uniquement le champ lui-même contre l'accès à partir d'autres classes, mais pas les références d'objet par ce champ. Si vous avez besoin de protéger un objet référencé, ne le donnez pas. Changement

public String [] getArr ()
{
    return arr;
}

à:

public String [] getArr ()
{
    return arr.clone ();
}

ou pour

public int getArrLength ()
{
    return arr.length;
}

public String getArrElementAt (int index)
{
    return arr [index];
}
45
Mikhail Vladimirov

Le Collections.unmodifiableList A déjà été mentionné - la Arrays.asList() étrangement pas! Ma solution serait également d'utiliser la liste de l'extérieur et d'envelopper le tableau comme suit:

String[] arr = new String[]{"1", "2"}; 
public List<String> getList() {
    return Collections.unmodifiableList(Arrays.asList(arr));
}

Le problème avec la copie du tableau est: si vous le faites chaque fois que vous accédez au code et que le tableau est grand, vous créerez certainement beaucoup de travail pour le garbage collector. La copie est donc une approche simple mais vraiment mauvaise - je dirais "pas cher", mais cher en mémoire! Surtout lorsque vous avez plus de 2 éléments.

Si vous regardez le code source de Arrays.asList Et Collections.unmodifiableList Il n'y a en fait pas grand-chose créé. Le premier encapsule simplement le tableau sans le copier, le second encapsule simplement la liste, rendant les modifications indisponibles.

28
michael_s

Vous pouvez également utiliser ImmutableList qui devrait être meilleur que le standard unmodifiableList. La classe fait partie des bibliothèques Guava créées par Google.

Voici la description:

Contrairement à Collections.unmodifiableList (Java.util.List), qui est une vue d'une collection distincte qui peut encore changer, une instance de ImmutableList contient ses propres données privées et ne changera jamais

Voici un exemple simple de son utilisation:

public class Test
{
  private String[] arr = new String[]{"1","2"};    

  public ImmutableList<String> getArr() 
  {
    return ImmutableList.copyOf(arr);
  }
}
6
iTech

à ce point de vue, vous devez utiliser la copie de la matrice système:

public String[] getArr() {
   if (arr != null) {
      String[] arrcpy = new String[arr.length];
      System.arraycopy(arr, 0, arrcpy, 0, arr.length);
      return arrcpy;
   } else
      return null;
   }
}
3
G M Ramesh

Vous pouvez renvoyer une copie des données. L'appelant qui choisit de modifier les données ne modifiera que la copie

public class Test {
    private static String[] arr = new String[] { "1", "2" };

    public String[] getArr() {

        String[] b = new String[arr.length];

        System.arraycopy(arr, 0, b, 0, arr.length);

        return b;
    }
}
2
artfullyContrived

Le nœud du problème est que vous renvoyez un pointeur vers un objet mutable. Oops. Soit vous rendez l'objet immuable (la solution de liste non modifiable), soit vous renvoyez une copie de l'objet.

D'une manière générale, la finalité des objets ne protège pas les objets d'être modifiés s'ils sont mutables. Ces deux problèmes sont "embrasser des cousins".

2
ncmathsadist

Renvoyer une liste non modifiable est une bonne idée. Mais une liste rendue non modifiable lors de l'appel à la méthode getter peut toujours être modifiée par la classe ou des classes dérivées de la classe.

Au lieu de cela, vous devez indiquer clairement à quiconque étend la classe que la liste ne doit pas être modifiée.

Ainsi, dans votre exemple, cela pourrait conduire au code suivant:

import Java.util.Arrays;
import Java.util.Collections;
import Java.util.List;

public class Test {
    public static final List<String> STRINGS =
        Collections.unmodifiableList(
            Arrays.asList("1", "2"));

    public final List<String> getStrings() {
        return STRINGS;
    }
}

Dans l'exemple ci-dessus, j'ai rendu public le champ STRINGS, en principe, vous pouvez supprimer l'appel de méthode, car les valeurs sont déjà connues.

Vous pouvez également affecter les chaînes à un private final List<String> champ rendu non modifiable lors de la construction de l'instance de classe. L'utilisation d'une constante ou d'arguments d'instanciation (du constructeur) dépend de la conception de la classe.

import Java.util.Arrays;
import Java.util.Collections;
import Java.util.List;

public class Test {
    private final List<String> strings;

    public Test(final String ... strings) {
        this.strings = Collections.unmodifiableList(Arrays
                .asList(strings));
    }

    public final List<String> getStrings() {
        return strings;
    }
}
1
Maarten Bodewes

Oui, vous devez renvoyer une copie du tableau:

 public String[] getArr()
 {
    return Arrays.copyOf(arr);
 }
0
kofemann