it-swarm.com.ru

Неизменяемый массив в Java

Есть ли неизменная альтернатива примитивным массивам в Java? Создание примитивного массива final на самом деле не мешает делать что-то вроде

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

Я хочу, чтобы элементы массива были неизменяемыми.

135
ignoramus

Не с примитивными массивами. Вам нужно будет использовать List или некоторую другую структуру данных:

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

Я рекомендую не использовать массив или unmodifiableList, а использовать Guava s ImmutableList , который существует для этой цели.

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

Как уже отмечали другие, в Java не может быть неизменных массивов.

Если вам абсолютно необходим метод, который возвращает массив, который не влияет на исходный массив, то вам придется каждый раз клонировать массив:

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

Очевидно, что это довольно дорого (так как вы будете создавать полную копию каждый раз, когда вызываете геттер), но если вы не можете изменить интерфейс (например, для использования List) и не рискуете, что клиент поменяет ваши внутренние компоненты, тогда это может быть необходимо.

Эта техника называется создание защитной копии.

20
Joachim Sauer

Есть один способ сделать неизменный массив в Java:

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

Массивы с 0 элементами (очевидно) не могут быть изменены.

Это может оказаться полезным, если вы используете метод List.toArray для преобразования List в массив. Поскольку даже пустой массив занимает некоторую память, вы можете сохранить это выделение памяти, создав постоянный пустой массив и всегда передавая его методу toArray. Этот метод выделит новый массив, если передаваемому массиву не хватает места, но если он есть (список пуст), он вернет переданный массив, что позволит вам повторно использовать этот массив каждый раз, когда вы вызываете toArray в пустой 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
14
Brigham

Еще один ответ

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

Если вам нужен (по соображениям производительности или для экономии памяти) нативный int, а не Java.lang.Integer, вам, вероятно, потребуется написать собственный класс-обертку. В сети есть различные реализации IntArray, но ни одна (я обнаружил) не была неизменной: Koders IntArray , Lucene IntArray . Есть, наверное, другие.

3
Thomas Mueller

Начиная с Java 9 вы можете использовать List.of(...), JavaDoc .

Этот метод возвращает неизменный List и очень эффективен.

3
rmuller

Начиная с Guava 22, из пакета com.google.common.primitives вы можете использовать три новых класса, которые занимают меньше места по сравнению с ImmutableList.

У них также есть строитель. Пример:

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

или, если размер известен во время компиляции:

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

Это еще один способ получить неизменный вид массива для примитивов Java.

2
JeanValjean

Нет, это невозможно. Тем не менее, можно сделать что-то вроде этого:

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

Это требует использования оболочек, и это список, а не массив, но это ближайший к вам.

1
user439793

В некоторых ситуациях будет проще использовать этот статический метод из библиотеки Google Guava: List<Integer> Ints.asList(int... backingArray)

Примеры:

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

Если вы хотите избежать как изменчивости, так и бокса, выхода из коробки нет. Но вы можете создать класс, который содержит массив примитивов и предоставляет доступ только для чтения к элементам через метод (ы).

1
Sarge Borsch

Метод of (E ... elements) в Java9 можно использовать для создания неизменяемого списка, используя только строку:

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

Вышеуказанный метод возвращает неизменный список, содержащий произвольное количество элементов. И добавление любого целого числа в этот список приведет к исключению Java.lang.UnsupportedOperationExceptionexception. Этот метод также принимает один массив в качестве аргумента.

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

Хотя верно то, что Collections.unmodifiableList() работает, иногда у вас может быть большая библиотека с уже определенными методами для возврата массивов (например, String[]) . Чтобы предотвратить их разбивку, вы можете определить вспомогательные массивы, в которых будут храниться значения:

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

Тестировать:

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

Не идеально, но, по крайней мере, у вас есть «псевдо неизменяемые массивы» (с точки зрения класса), и это не нарушит связанный код.

0
miguelt