Tableau immuable en Java

158

Existe-t-il une alternative immuable aux tableaux primitifs en Java? Créer un tableau primitif finaln'empêche pas en fait de faire quelque chose comme

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

Je veux que les éléments du tableau soient inchangeables.


la source
43
Rendez-vous service et arrêtez d'utiliser les tableaux en Java pour autre chose que 1) io 2) le calcul des nombres lourds 3) si vous avez besoin d'implémenter votre propre liste / collection (ce qui est rare ). Ils sont extrêmement inflexibles et désuets ... comme vous venez de le découvrir avec cette question.
whaley
39
@Whaley, et des performances, et du code où vous n'avez pas besoin de tableaux "dynamiques". Les tableaux sont toujours utiles dans de nombreux endroits, ce n'est pas si rare.
Colin Hebert
10
@Colin: oui mais ils sont très contraignants; il vaut mieux prendre l'habitude de penser "ai-je vraiment besoin d'un tableau ici ou puis-je utiliser une liste à la place?"
Jason S
7
@Colin: n'optimisez que lorsque vous en avez besoin. Lorsque vous passez une demi-minute à ajouter quelque chose qui, par exemple, agrandit un tableau ou que vous gardez un index en dehors de la portée d'une boucle for, vous avez déjà perdu une partie du temps de votre patron. Construisez d'abord, optimisez quand et où c'est nécessaire - et dans la plupart des applications, il ne s'agit pas de remplacer les listes par des tableaux.
fwielstra
9
Eh bien, pourquoi personne n'a mentionné int[]et new int[]est BEAUCOUP plus facile à taper que List<Integer>et new ArrayList<Integer>? XD
lcn

Réponses:

164

Pas avec les tableaux primitifs. Vous devrez utiliser une liste ou une autre structure de données:

List<Integer> items = Collections.unmodifiableList(Arrays.asList(0,1,2,3));
Jason S
la source
18
D'une manière ou d'une autre, je n'ai jamais connu Arrays.asList (T ...). Je suppose que je peux me débarrasser de ma ListUtils.list (T ...) maintenant.
MattRS
3
À propos, Arrays.asList donne une liste non modifiable
mauhiz
3
@tony que ArrayList n'est pas de java.util
Mauhiz
11
@mauhiz Arrays.asListn'est pas non modifiable. docs.oracle.com/javase/7/docs/api/java/util/… "Renvoie une liste de taille fixe soutenue par le tableau spécifié. (Modifications apportées à la liste renvoyée" écrivez "sur le tableau.)"
Jason S
7
@JasonS eh bien, semble enArrays.asList() effet non modifiable dans le sens où vous ne pouvez pas ou des éléments du retourné (à ne pas confondre avec ) - ces opérations ne sont tout simplement pas implémentées. Peut-être que @mauhiz essaie de dire ça? Mais bien sûr, vous pouvez modifier les éléments existants avec , donc certainement pas unmofiable , QED a ajouté le commentaire juste pour souligner la différence entre le résultat de et normaladdremovejava.util.Arrays.ArrayListjava.util.ArrayListList<> aslist = Arrays.asList(...); aslist.set(index, element)java.util.Arrays.ArrayListasListArrayList
NIA
73

Ma recommandation est de ne pas utiliser un tableau ou un unmodifiableListmais d'utiliser goyave de ImmutableList , qui existe à cet effet.

ImmutableList<Integer> values = ImmutableList.of(0, 1, 2, 3);
ColinD
la source
3
+1 Guava's ImmutableList est encore mieux que Collections.unmodifiableList, car il s'agit d'un type distinct.
sleske
29
ImmutableList est souvent meilleur (cela dépend du cas d'utilisation) car il est immuable. Collections.unmodifiableList n'est pas immuable. C'est plutôt une vue que les récepteurs ne peuvent pas changer, mais la source originale PEUT changer.
Charlie Collins
2
@CharlieCollins, s'il n'y a aucun moyen d'accéder à la source originale qui est Collections.unmodifiableListsuffisant pour rendre une liste immuable?
savanibharat
1
@savanibharat Oui.
Juste un étudiant
20

Comme d'autres l'ont noté, vous ne pouvez pas avoir de tableaux immuables en Java.

Si vous avez absolument besoin d'une méthode qui renvoie un tableau qui n'influence pas le tableau d'origine, vous devez cloner le tableau à chaque fois:

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

Évidemment, cela coûte assez cher (car vous créerez une copie complète à chaque fois que vous appelez le getter), mais si vous ne pouvez pas changer l'interface (pour utiliser un Listpar exemple) et que vous ne pouvez pas risquer que le client change vos données internes, alors cela peut être nécessaire.

Cette technique s'appelle faire une copie défensive.

Joachim Sauer
la source
3
Où a-t-il mentionné qu'il avait besoin d'un getter?
Erick Robertson
6
@Erik: il ne l'a pas fait, mais c'est un cas d'utilisation très courant pour les structures de données immuables (j'ai modifié la réponse pour faire référence aux méthodes en général, car la solution s'applique partout, même si elle est plus courante dans les getters).
Joachim Sauer
7
Vaut-il mieux utiliser clone()ou Arrays.copy()ici?
kevinarpe
Pour autant que je me souvienne, selon "Effective Java" de Joshua Bloch, clone () est définitivement le moyen préféré.
Vincent
1
Dernière phrase de l'élément 13, "Remplacer judicieusement le clonage": "En règle générale, la fonctionnalité de copie est mieux fournie par les constructeurs ou les usines. Une exception notable à cette règle est les tableaux, qui sont mieux copiés avec la méthode de clonage."
Vincent
14

Il existe une façon de créer un tableau immuable en Java:

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

Les tableaux avec 0 élément (évidemment) ne peuvent pas être mutés.

Cela peut en fait être utile si vous utilisez la List.toArrayméthode pour convertir un Listen tableau. Puisque même un tableau vide prend de la mémoire, vous pouvez enregistrer cette allocation de mémoire en créant un tableau vide constant et en le passant toujours à la toArrayméthode. Cette méthode allouera un nouveau tableau si le tableau que vous passez n'a pas assez d'espace, mais si c'est le cas (la liste est vide), il renverra le tableau que vous avez passé, vous permettant de réutiliser ce tableau à chaque fois que vous appelez toArrayun vide 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
Brigham
la source
3
Mais cela n'aide pas l'OP à obtenir un tableau immuable contenant des données.
Sridhar Sarnobat
1
@ Sridhar-Sarnobat C'est un peu le but de la réponse. Il n'y a aucun moyen en Java de créer un tableau immuable contenant des données.
Brigham le
1
J'aime mieux cette syntaxe plus simple: chaîne finale statique privée [] EMPTY_STRING_ARRAY = {}; (De toute évidence, je n'ai pas compris comment faire "mini-Markdown". Un bouton de prévisualisation serait bien.)
Wes
6

Une autre réponse

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éracode
la source
à quoi sert de copier ce tableau dans le constructeur, puisque nous n'avons fourni aucune méthode setter?
vijaya kumar
Un contenu du tableau d'entrée peut toujours être modifié ultérieurement. Nous voulons conserver son état d'origine, nous le copions donc.
aeracode
4

A partir de Java 9 vous pouvez utiliser List.of(...), JavaDoc .

Cette méthode retourne un immuable Listet est très efficace.

rmuller
la source
3

Si vous avez besoin (pour des raisons de performances ou pour économiser de la mémoire) natif 'int' au lieu de 'java.lang.Integer', alors vous devrez probablement écrire votre propre classe wrapper. Il existe diverses implémentations d'IntArray sur le net, mais aucune (j'ai trouvé) n'était immuable: Koders IntArray , Lucene IntArray . Il y en a probablement d'autres.

Thomas Mueller
la source
3

Depuis Guava 22, à partir de package, com.google.common.primitivesvous pouvez utiliser trois nouvelles classes, qui ont une empreinte mémoire inférieure à ImmutableList.

Ils ont également un constructeur. Exemple:

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

ou, si la taille est connue au moment de la compilation:

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

C'est une autre façon d'obtenir une vue immuable d'un tableau pour les primitives Java.

Jean Valjean
la source
1

Non, ce n'est pas possible. Cependant, on pourrait faire quelque chose comme ceci:

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

Cela nécessite l'utilisation de wrappers, et c'est une liste, pas un tableau, mais c'est le plus proche que vous obtiendrez.


la source
4
Pas besoin d'écrire tous ces valueOf()s, l'auto-box s'en chargera. Aussi Arrays.asList(0, 2, 3, 4)serait beaucoup plus concis.
Joachim Sauer
@Joachim: Le point d'utilisation valueOf()est d'utiliser le cache d'objets Integer interne pour réduire la consommation / le recyclage de la mémoire.
Esko du
4
@Esko: lisez les spécifications de l'auto-box. Cela fait exactement la même chose, donc il n'y a pas de différence ici.
Joachim Sauer
1
@John Vous n'obtiendrez jamais un NPE convertissant un inten un Integercependant; il suffit de faire attention à l'inverse.
ColinD le
1
@John: tu as raison, ça peut être dangereux. Mais au lieu de l'éviter complètement, il vaut probablement mieux comprendre le danger et l'éviter.
Joachim Sauer
1

Dans certaines situations, il sera plus léger d'utiliser cette méthode statique de la bibliothèque Google Guava: List<Integer> Ints.asList(int... backingArray)

Exemples:

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

Si vous voulez éviter à la fois la mutabilité et la boxe, il n'y a aucun moyen de sortir de la boîte. Mais vous pouvez créer une classe qui contient un tableau primitif et fournit un accès en lecture seule aux éléments via des méthodes.

Afficher un nom
la source
1

La méthode of (E ... elements) en Java9 peut être utilisée pour créer une liste immuable en utilisant juste une ligne:

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

La méthode ci-dessus renvoie une liste immuable contenant un nombre arbitraire d'éléments. Et ajouter n'importe quel entier à cette liste entraînerait une java.lang.UnsupportedOperationExceptionexception. Cette méthode accepte également un seul tableau comme argument.

String[] array = ... ;
List<String[]> list = List.<String[]>of(array);
akhil_mittal
la source
0

Bien qu'il soit vrai que cela Collections.unmodifiableList()fonctionne, vous pouvez parfois avoir une grande bibliothèque avec des méthodes déjà définies pour renvoyer des tableaux (par exemple String[]). Pour éviter de les casser, vous pouvez en fait définir des tableaux auxiliaires qui stockeront les valeurs:

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

Tester:

    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()));

Pas parfait, mais au moins vous avez des "pseudo tableaux immuables" (du point de vue de la classe) et cela ne cassera pas le code associé.

Miguelt
la source
-3

Eh bien .. les tableaux sont utiles pour passer en tant que constantes (si elles l'étaient) en tant que paramètres de variantes.

Martin
la source
Qu'est-ce qu'un "paramètre de variantes"? Jamais entendu parler.
sleske
2
L'utilisation de tableaux comme constantes est exactement là où de nombreuses personnes échouent. La référence est constante, mais le contenu du tableau est modifiable. Un appelant / client peut changer le contenu de votre «constante». Ce n'est probablement pas quelque chose que vous souhaitez autoriser. Utilisez une ImmutableList.
Charlie Collins
@CharlieCollins même cela peut être piraté par réflexion. Java est un langage dangereux en termes de mutabilité ... et cela ne va pas changer.
Afficher le nom le
2
@SargeBorsch C'est vrai, mais quiconque fait du piratage basé sur la réflexion doit savoir qu'il joue avec le feu en modifiant des choses auxquelles l'éditeur de l'API n'aurait peut-être pas voulu qu'il accède. Alors que, si une API renvoie un int[], l'appelant peut bien supposer qu'il peut faire ce qu'il veut avec ce tableau sans affecter les composants internes de l'API.
daiscog