Matrice immutabile in Java


158

Esiste un'alternativa immutabile agli array primitivi in ​​Java? Realizzare un array primitivo finalnon impedisce in realtà di fare qualcosa del genere

final int[] array = new int[] {0, 1, 2, 3};
array[0] = 42;

Voglio che gli elementi dell'array siano immutabili.


43
Fatevi un favore e smettete di usare array in Java per qualsiasi cosa oltre a 1) io 2) scricchiolio di numeri pesanti 3) se avete bisogno di implementare la vostra Lista / Collezione (cosa rara ). Sono estremamente inflessibili e antiquati ... come hai appena scoperto con questa domanda.
Whaley,

39
@Whaley, performance e codice in cui non hai bisogno di array "dinamici". Le matrici sono ancora utili in molti posti, non è così raro.
Colin Hebert,

10
@Colin: sì ma sono fortemente vincolanti; è meglio prendere l'abitudine di pensare "ho davvero bisogno di un array qui o posso usare un elenco?"
Jason S,

7
@Colin: ottimizza solo quando è necessario. Quando ti ritrovi a dedicare mezzo minuto ad aggiungere qualcosa che, ad esempio, allarga un array o mantieni un indice al di fuori dell'ambito di un ciclo continuo, hai già perso un po 'di tempo del tuo capo. Costruisci prima, ottimizza quando e dove è necessario - e nella maggior parte delle applicazioni, ciò non significa sostituire gli elenchi con array.
fwielstra,

9
Bene, perché nessuno ha menzionato int[]ed new int[]è MOLTO più facile da scrivere di List<Integer>e new ArrayList<Integer>? XD
lcn,

Risposte:


164

Non con array primitivi. Dovrai utilizzare un elenco o qualche altra struttura di dati:

List<Integer> items = Collections.unmodifiableList(Arrays.asList(0,1,2,3));

18
In qualche modo non ho mai saputo di Arrays.asList (T ...). Immagino di poter sbarazzarmi del mio ListUtils.list (T ...) ora.
MattRS

3
A proposito, Arrays.asList fornisce un elenco non modificabile
mauhiz,

3
@tony che ArrayList non è di java.util
mauhiz,

11
@mauhiz nonArrays.asList è modificabile. docs.oracle.com/javase/7/docs/api/java/util/… "Restituisce un elenco di dimensioni fisse supportato dall'array specificato. (Le modifiche all'elenco restituito" scrivono "all'array.)"
Jason S,

7
@JasonS bene, Arrays.asList()anzi sembra immodificabile, nel senso che non si può addo removevoci di restituiti java.util.Arrays.ArrayList( da non confondere con java.util.ArrayList) - queste operazioni solo non vengono attuate. Forse @mauhiz prova a dire questo? Ma ovviamente puoi modificare gli elementi esistenti con List<> aslist = Arrays.asList(...); aslist.set(index, element), quindi java.util.Arrays.ArrayListnon è certo impossibile , QED Aggiunto il commento solo per sottolineare la differenza tra il risultato di asListe normaleArrayList
NIA

73

La mia raccomandazione è di non usare un array o un unmodifiableListma di usare Guava 's ImmutableList , che esiste a questo scopo.

ImmutableList<Integer> values = ImmutableList.of(0, 1, 2, 3);

3
+1 Guava's ImmutableList è persino meglio di Collections.unmodifiableList, perché è un tipo separato.
sleske,

29
ImmutableList è spesso migliore (dipende dal caso d'uso) perché è immutabile. Collections.unmodifiableList non è immutabile. Piuttosto, è una vista che i ricevitori non possono cambiare, ma la fonte originale PU change cambiare.
Charlie Collins,

2
@CharlieCollins, se non c'è modo di accedere alla fonte originale di quanto sia Collections.unmodifiableListsufficiente a rendere immutabile un Elenco?
savanibharat,

1
@savanibharat Sì.
Solo uno studente il

20

Come altri hanno notato, non è possibile avere array immutabili in Java.

Se hai assolutamente bisogno di un metodo che restituisca un array che non influenza l'array originale, allora dovresti clonare l'array ogni volta:

public int[] getFooArray() {
  return fooArray == null ? null : fooArray.clone();
}

Ovviamente questo è piuttosto costoso (poiché ne creerai una copia completa ogni volta che chiami il getter), ma se non riesci a cambiare l'interfaccia (per usare un List esempio) e non puoi rischiare che il client cambi i tuoi interni, allora potrebbe essere necessario.

Questa tecnica si chiama fare una copia difensiva.


3
Dove ha detto che aveva bisogno di un getter?
Erick Robertson,

6
@Erik: non l'ha fatto, ma è un caso d'uso molto comune per strutture di dati immutabili (ho modificato la risposta per fare riferimento ai metodi in generale, poiché la soluzione si applica ovunque, anche se è più comune nei getter).
Joachim Sauer,

7
È meglio usare clone()o Arrays.copy()qui?
kevinarpe,

Per quanto mi ricordo, secondo "Effective Java" di Joshua Bloch, clone () è sicuramente il modo preferito.
Vincent

1
Ultima frase dell'articolo 13, "Sostituisci giudiziosamente il clone": "Di norma, la funzionalità di copia è fornita al meglio da costruttori o fabbriche. Un'eccezione notevole a questa regola sono le matrici, che sono meglio copiate con il metodo clone".
Vincent

14

C'è un modo per creare un array immutabile in Java:

final String[] IMMUTABLE = new String[0];

Le matrici con 0 elementi (ovviamente) non possono essere mutate.

Questo può effettivamente tornare utile se si utilizza il List.toArraymetodo per convertire Listun array. Poiché anche un array vuoto occupa un po 'di memoria, è possibile salvare l'allocazione di memoria creando un array vuoto costante e passandolo sempre al toArraymetodo. Questo metodo allocherà un nuovo array se l'array che passi non ha abbastanza spazio, ma se lo fa (l'elenco è vuoto), restituirà l'array che hai passato, permettendoti di riutilizzarlo ogni volta che chiami toArrayun svuotare List.

final static String[] EMPTY_STRING_ARRAY = new String[0];

List<String> emptyList = new ArrayList<String>();
return emptyList.toArray(EMPTY_STRING_ARRAY); // returns EMPTY_STRING_ARRAY

3
Ma ciò non aiuta l'OP a raggiungere un array immutabile con dati al suo interno.
Sridhar Sarnobat,

1
@ Sridhar-Sarnobat Questo è un po 'il punto della risposta. In Java non è possibile creare un array immutabile con dati al suo interno.
Brigham,

1
Mi piace meglio questa sintassi più semplice: stringa statica finale privata [] EMPTY_STRING_ARRAY = {}; (Ovviamente non ho capito come fare "mini-markdown". Un pulsante di anteprima sarebbe carino.)
Wes

6

Un'altra risposta

static class ImmutableArray<T> {
    private final T[] array;

    private ImmutableArray(T[] a){
        array = Arrays.copyOf(a, a.length);
    }

    public static <T> ImmutableArray<T> from(T[] a){
        return new ImmutableArray<T>(a);
    }

    public T get(int index){
        return array[index];
    }
}

{
    final ImmutableArray<String> sample = ImmutableArray.from(new String[]{"a", "b", "c"});
}

a che serve copiare quell'array nel costruttore, dal momento che non abbiamo fornito alcun metodo setter?
Vijaya Kumar,

Un contenuto dell'array di input può ancora essere modificato in seguito. Vogliamo preservare il suo stato originale, quindi lo copiamo.
codice aereo

4

Come di Java 9 è possibile utilizzare List.of(...), JavaDoc .

Questo metodo restituisce un valore immutabile Listed è molto efficiente.


3

Se è necessario (per motivi di prestazioni o per risparmiare memoria) 'int' nativo invece di 'java.lang.Integer', probabilmente è necessario scrivere la propria classe wrapper. Esistono varie implementazioni IntArray in rete, ma nessuna (ho scoperto) era immutabile: Koders IntArray , Lucene IntArray . Ce ne sono probabilmente altri.


3

Da Guava 22, dal pacchetto com.google.common.primitivesè possibile utilizzare tre nuove classi, che hanno un footprint di memoria inferiore rispetto a ImmutableList.

Hanno anche un costruttore. Esempio:

int size = 2;
ImmutableLongArray longArray = ImmutableLongArray.builder(size)
  .add(1L)
  .add(2L)
  .build();

oppure, se la dimensione è nota al momento della compilazione:

ImmutableLongArray longArray = ImmutableLongArray.of(1L, 2L);

Questo è un altro modo per ottenere una vista immutabile di un array per primitive Java.


1

No, non è possibile. Tuttavia, si potrebbe fare qualcosa del genere:

List<Integer> temp = new ArrayList<Integer>();
temp.add(Integer.valueOf(0));
temp.add(Integer.valueOf(2));
temp.add(Integer.valueOf(3));
temp.add(Integer.valueOf(4));
List<Integer> immutable = Collections.unmodifiableList(temp);

Ciò richiede l'uso di wrapper, ed è un elenco, non un array, ma è il più vicino che otterrai.


4
Non c'è bisogno di scrivere tutti questi messaggi valueOf(), l'autoboxing se ne occuperà. Inoltre Arrays.asList(0, 2, 3, 4)sarebbe molto più conciso.
Joachim Sauer,

@Joachim: il punto di utilizzo valueOf()è utilizzare la cache di oggetti Integer interna per ridurre il consumo / riciclaggio della memoria.
Esko,

4
@Esko: leggi le specifiche di autoboxing. Fa esattamente la stessa cosa, quindi non c'è differenza qui.
Joachim Sauer,

1
@John Non avrai mai un NPE che converte intin un Integerperò; devi solo stare attento al contrario.
ColinD,

1
@Giovanni: hai ragione, può essere pericoloso. Ma invece di evitarlo completamente, probabilmente è meglio capire il pericolo ed evitarlo.
Joachim Sauer,

1

In alcune situazioni, sarà più leggero utilizzare questo metodo statico dalla libreria di Google Guava: List<Integer> Ints.asList(int... backingArray)

Esempi:

  • List<Integer> x1 = Ints.asList(0, 1, 2, 3)
  • List<Integer> x1 = Ints.asList(new int[] { 0, 1, 2, 3})

1

Se si desidera evitare sia la mutabilità che il pugilato, non c'è via d'uscita. Ma puoi creare una classe che contiene un array primitivo e fornisce un accesso di sola lettura agli elementi tramite i metodi.


1

Il metodo of (E ... elements) in Java9 può essere usato per creare un elenco immutabile usando solo una riga:

List<Integer> items = List.of(1,2,3,4,5);

Il metodo sopra riportato restituisce un elenco immutabile contenente un numero arbitrario di elementi. E l'aggiunta di qualsiasi numero intero a questo elenco comporterebbe java.lang.UnsupportedOperationExceptionun'eccezione. Questo metodo accetta anche un singolo array come argomento.

String[] array = ... ;
List<String[]> list = List.<String[]>of(array);

0

Mentre è vero che Collections.unmodifiableList()funziona, a volte potresti avere una grande libreria con metodi già definiti per restituire array (ad es String[].). Per evitare di romperli, puoi effettivamente definire array ausiliari che memorizzeranno i valori:

public class Test {
    private final String[] original;
    private final String[] auxiliary;
    /** constructor */
    public Test(String[] _values) {
        original = new String[_values.length];
        // Pre-allocated array.
        auxiliary = new String[_values.length];
        System.arraycopy(_values, 0, original, 0, _values.length);
    }
    /** Get array values. */
    public String[] getValues() {
        // No need to call clone() - we pre-allocated auxiliary.
        System.arraycopy(original, 0, auxiliary, 0, original.length);
        return auxiliary;
    }
}

Testare:

    Test test = new Test(new String[]{"a", "b", "C"});
    System.out.println(Arrays.asList(test.getValues()));
    String[] values = test.getValues();
    values[0] = "foobar";
    // At this point, "foobar" exist in "auxiliary" but since we are 
    // copying "original" to "auxiliary" for each call, the next line
    // will print the original values "a", "b", "c".
    System.out.println(Arrays.asList(test.getValues()));

Non perfetto, ma almeno hai "matrici pseudo immutabili" (dal punto di vista della classe) e questo non romperà il codice correlato.


-3

Bene ... le matrici sono utili per passare come costanti (se lo fossero) come parametri varianti.


Che cos'è un "parametro varianti"? Non ne ho mai sentito parlare.
sleske,

2
L'uso delle matrici come costanti è esattamente il punto in cui molte persone FALLISCONO. Il riferimento è costante, ma i contenuti dell'array sono mutabili. Un chiamante / client potrebbe cambiare il contenuto della tua "costante". Questo probabilmente non è qualcosa che vuoi permettere. Usa un ImmutableList.
Charlie Collins,

@CharlieCollins anche questo può essere hackerato tramite la riflessione. Java è un linguaggio non sicuro in termini di mutabilità ... e questo non cambierà.
Visualizza nome

2
@SargeBorsch È vero, ma chiunque faccia un hacking basato sulla riflessione dovrebbe sapere che stanno giocando con il fuoco modificando le cose a cui l'editore API potrebbe non aver voluto accedere. Considerando che, se un'API restituisce un int[], il chiamante potrebbe benissimo presumere di poter fare ciò che desidera con quell'array senza influire sugli interni dell'API.
daiscog,
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.