Come posso impedire la modifica di un campo privato in una classe?


165

Immagina di avere questa classe:

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

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

Ora, ho un'altra classe che utilizza la classe sopra:

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

Quindi questo è il problema: ho avuto accesso a un campo privato di una classe dall'esterno! Come posso impedirlo? Voglio dire, come posso rendere questo array immutabile? Questo significa che con ogni metodo getter puoi salire di livello per accedere al campo privato? (Non voglio biblioteche come Guava. Devo solo conoscere il modo giusto per farlo).


10
In realtà, il che rende finalnon impedisce la modifica del campo . Tuttavia, prevenire la modifica di Objectcui fa riferimento un campo è più complicato.
OldCurmudgeon,

22
C'è un problema con il tuo modello mentale se pensi di poter modificare un array in cui hai un riferimento memorizzato in un campo privato equivale a poter modificare un campo privato.
Joren,

45
Se è privato, perché esporlo in primo luogo?
Erik Reppen,

7
Mi ci è voluto un po 'per capirlo, ma migliora sempre il mio codice quando mi assicuro che tutte le strutture di dati siano completamente incapsulate nel loro oggetto - il che significa che non c'è modo di "ottenere" il tuo "arr", invece fai tutto quello che devi all'interno la classe o fornire un iteratore.
Bill K,

8
Qualcun altro è anche sorpreso del perché questa domanda abbia avuto così tanta attenzione?
Romano,

Risposte:


163

È necessario restituire una copia dell'array.

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

121
Vorrei ricordare alla gente che sebbene questa sia stata votata come la risposta corretta alla domanda, la migliore soluzione al problema è in realtà come dice sp00m - restituire un Unmodifiable List.
OldCurmudgeon,

48
Non se in realtà è necessario restituire un array.
Svish,

8
In realtà, la migliore soluzione al problema discusso è spesso come dice @MikhailVladimirov: - per fornire o restituire una vista dell'array o della raccolta.
Christoffer Hammarström,

20
ma assicurati che sia una copia profonda, non una copia superficiale, dei dati. Per le stringhe non fa alcuna differenza, per altre classi come la data in cui è possibile modificare il contenuto dell'istanza può fare la differenza.
jwenting

16
@Svish Avrei dovuto essere più drastico: se restituisci un array da una funzione API lo stai facendo in modo sbagliato. Nelle funzioni private all'interno di una libreria potrebbe essere giusto. In un'API (dove la protezione contro la modificabilità gioca un ruolo), non lo è mai .
Konrad Rudolph,

377

Se è possibile utilizzare un Elenco anziché un array, Collezioni fornisce un elenco non modificabile :

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

Aggiunta una risposta che utilizza Collections.unmodifiableList(list)come assegnazione al campo, per assicurarsi che l'elenco non venga modificato dalla classe stessa.
Maarten Bodewes,

Mi chiedo solo, se decompili questo metodo otterrai molte nuove parole chiave. Ciò non influisce negativamente sulle prestazioni quando getList () accede molto. Ad esempio nel codice di rendering.
Thomas15v,

2
Si noti che questo funziona se gli elementi degli array sono immutabili in se stessi String è, ma se sono mutabili potrebbe essere necessario fare qualcosa per impedire al chiamante di cambiarli.
Lii,

45

Il modificatore privateprotegge solo il campo stesso dall'accesso da altre classi, ma non i riferimenti agli oggetti da questo campo. Se devi proteggere un oggetto referenziato, non darlo. Modificare

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

per:

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

o a

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

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

5
Questo articolo non discute contro l'utilizzo del clone sugli array, ma solo sugli oggetti che possono essere sottoposti a sottoclasse.
Lyn Headley,

28

Il Collections.unmodifiableListè già stato menzionato - il Arrays.asList()stranamente no! La mia soluzione sarebbe anche quella di utilizzare l'elenco dall'esterno e avvolgere l'array come segue:

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

Il problema con la copia dell'array è: se lo fai ogni volta che accedi al codice e l'array è grande, creerai sicuramente molto lavoro per il Garbage Collector. Quindi la copia è un approccio semplice ma davvero pessimo - direi "economico", ma costoso per la memoria! Soprattutto quando hai più di 2 soli elementi.

Se guardi il codice sorgente di Arrays.asListe in Collections.unmodifiableListrealtà non c'è molto da creare. Il primo avvolge l'array senza copiarlo, il secondo avvolge semplicemente l'elenco, rendendolo non disponibile.


Si suppone qui che l'array, comprese le stringhe, venga copiato ogni volta. Non lo è, vengono copiati solo i riferimenti alle stringhe immutabili. Finché l'array non è enorme, l'overhead è trascurabile. Se l'array è enorme, vai agli elenchi e immutableListfino in fondo!
Maarten Bodewes

No, non ho fatto quell'ipotesi - dove? Riguarda i riferimenti che vengono copiati - non importa se si tratta di una stringa o di qualsiasi altro oggetto. Dico solo che per array di grandi dimensioni non consiglierei di copiare l'array e che le operazioni Arrays.asListe Collections.unmodifiableListsono probabilmente più economiche per array di dimensioni maggiori. Forse qualcuno può calcolare quanti elementi sono necessari, ma ho già visto il codice in for-loop con array contenenti migliaia di elementi che sono stati copiati solo per impedire la modifica - è pazzesco!
michael_s,

6

Puoi anche usare ImmutableListquale dovrebbe essere migliore dello standard unmodifiableList. La classe fa parte di Guava librerie create da Google.

Ecco la descrizione:

A differenza di Collections.unmodifiableList (java.util.List), che è una vista di una raccolta separata che può ancora cambiare, un'istanza di ImmutableList contiene i propri dati privati ​​e non cambierà mai

Ecco un semplice esempio di come usarlo:

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

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

Utilizzare questa soluzione se non ci si può fidare della Testclasse per lasciare inalterato l'elenco restituito . È necessario assicurarsi che ImmutableListvenga restituito e che anche gli elementi dell'elenco siano immutabili. Altrimenti anche la mia soluzione dovrebbe essere sicura.
Maarten Bodewes,

3

a questo punto di vista dovresti usare la copia dell'array di sistema:

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;
   }
}

-1 in quanto non fa nulla sulla chiamata clonee non è così conciso.
Maarten Bodewes,

2

È possibile restituire una copia dei dati. Il chiamante che sceglie di modificare i dati cambierà solo la copia

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

Il nocciolo del problema è che stai restituendo un puntatore a un oggetto mutabile. Ops. O si rende l'oggetto immutabile (la soluzione dell'elenco non modificabile) o si restituisce una copia dell'oggetto.

In generale, la finalità degli oggetti non protegge gli oggetti dal cambiamento se sono mutabili. Questi due problemi sono "baciare i cugini".


Bene Java ha dei riferimenti, mentre C ha dei puntatori. È stato detto abbastanza. Inoltre, il modificatore "finale" può avere più impatti a seconda di dove viene applicato. L'applicazione su un membro della classe ti impone di assegnare valore al membro esattamente una volta sola con l'operatore "=" (assegnazione). Questo non ha nulla a che fare con l'immutabilità. Se si sostituisce il riferimento di un membro a uno nuovo (qualche altra istanza della stessa classe), l'istanza precedente rimarrà invariata (ma potrebbe essere GCed se non sono presenti altri riferimenti).
Gyorgyabraham,

1

Restituire un elenco non modificabile è una buona idea. Ma un elenco reso non modificabile durante la chiamata al metodo getter può ancora essere modificato dalla classe o dalle classi derivate dalla classe.

Invece dovresti chiarire a chiunque estenda la classe che l'elenco non dovrebbe essere modificato.

Quindi nel tuo esempio potrebbe portare al seguente codice:

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;
    }
}

Nell'esempio sopra ho realizzato il STRINGS pubblico campo, in linea di principio potresti eliminare la chiamata al metodo, poiché i valori sono già noti.

È inoltre possibile assegnare le stringhe a un private final List<String>campo reso non modificabile durante la costruzione dell'istanza di classe. L'uso di una costante o argomenti di istanza (del costruttore) dipende dal design della 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;
    }
}

0

Sì, è necessario restituire una copia dell'array:

 public String[] getArr()
 {
    return Arrays.copyOf(arr);
 }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.