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.