web-dev-qa-db-fra.com

Tableau immuable en Java

Existe-t-il une alternative immuable aux tableaux primitifs en Java? Faire un tableau primitif final n'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 ne soient pas modifiables.

135
ignoramus

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));
148
Jason S

Ma recommandation est de ne pas utiliser un tableau ou une unmodifiableList mais d'utiliser Guava 's ImmutableList , qui existe à cette fin.

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

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 retourne un tableau qui n'influence pas le tableau d'origine, vous devez alors cloner le tableau à chaque fois:

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

Évidemment, cela coûte assez cher (vous allez créer une copie complète chaque fois que vous appelez le getter), mais si vous ne pouvez pas modifier l’interface (pour utiliser un List par exemple) et ne pouvez pas risquer que le client modifie vos données internes, alors il peut être nécessaire.

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

20
Joachim Sauer

Il existe un moyen de créer un tableau immuable en Java:

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

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

Cela peut s'avérer utile si vous utilisez la méthode List.toArray pour convertir un List en un tableau. Étant donné que même un tableau vide utilise 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 méthode toArray. 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), elle retournera le tableau que vous avez passé, ce qui vous permettra de le réutiliser chaque fois que vous appelez toArray sur une List vide.

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
14
Brigham

Une autre une 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"});
}
5
aeracode

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

3
Thomas Mueller

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

Cette méthode retourne une List immuable et est très efficace.

3
rmuller

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

Ils ont aussi 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 un autre moyen d'obtenir une vue immuable d'un tableau pour les primitives Java.

2
JeanValjean

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

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. Il s'agit d'une liste, pas d'un tableau, mais du plus proche possible.

1
user439793

Dans certaines situations, il sera plus léger d’utiliser cette méthode statique à partir 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})
1
kevinarpe

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

1
Sarge Borsch

La méthode de (éléments E ...) dans Java9 peut être utilisée pour créer une liste immuable en utilisant simplement 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 un entier à cette liste entraînerait une exception Java.lang.UnsupportedOperationExceptionexception. Cette méthode accepte également un seul tableau en tant qu'argument.

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

S'il est vrai que Collections.unmodifiableList() fonctionne, vous pouvez parfois disposer d'une grande bibliothèque ayant des méthodes déjà définies pour renvoyer des tableaux (par exemple, String[]) . Pour éviter de les casser, vous pouvez 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 "tableaux pseudo immuables" (du point de vue de la classe) et cela ne cassera pas le code associé.

0
miguelt