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

158

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

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

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


источник
43
Сделайте себе одолжение и прекратите использование массивов в Java для чего-либо, кроме 1) io 2) тяжелое сокращение числа 3) если вам нужно реализовать свой собственный список / коллекцию (что встречается редко ). Они чрезвычайно негибкие и устаревшие ... как вы только что обнаружили с этим вопросом.
кит
39
@ Whaley, и выступления, и код, где вам не нужны «динамические» массивы. Массивы все еще полезны во многих местах, это не так уж редко.
Колин Хеберт
10
@Colin: да, но они строго ограничивают; Лучше всего привыкнуть думать: «Мне действительно нужен массив или я могу вместо него использовать List?»
Джейсон С
7
@Colin: оптимизировать только тогда, когда вам нужно. Когда вы обнаружите, что тратите полминуты на добавление чего-либо, например, для увеличения массива или для того, чтобы оставить какой-то индекс вне рамок цикла for, вы уже потратили немного времени своего босса. Сначала создайте, оптимизируйте, когда и где это необходимо - и в большинстве приложений это не означает замену списков массивами.
fwielstra
9
Ну, почему никто не упомянул, int[]и new int[]НАМНОГО проще печатать, чем List<Integer>и new ArrayList<Integer>? XD
2013 г.

Ответы:

164

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

List<Integer> items = Collections.unmodifiableList(Arrays.asList(0,1,2,3));
Джейсон С
источник
18
Почему-то я никогда не знал об Arrays.asList (T ...). Я думаю, я могу избавиться от моего ListUtils.list (T ...) сейчас.
MattRS
3
Кстати, Arrays.asList дает неизменяемый список
mauhiz
3
@tony, что ArrayList не java.util's
mauhiz
11
@mauhiz неArrays.asList является неизменяемым. docs.oracle.com/javase/7/docs/api/java/util/… "Возвращает список фиксированного размера, поддерживаемый указанным массивом. (Изменения в возвращенном списке" записывают "в массив.)"
Джейсон С
7
@JasonS ну, Arrays.asList()действительно, кажется неизменным в том смысле, что вы не можете addили removeэлементы возвращаемых java.util.Arrays.ArrayList( не путать с java.util.ArrayList) - эти операции просто не реализованы. Может быть @mauhiz пытается это сказать? Но, конечно, вы можете изменять существующие элементы с помощью QED List<> aslist = Arrays.asList(...); aslist.set(index, element), поэтому, java.util.Arrays.ArrayListконечно, это не является неизменным. Добавлен комментарий только для того, чтобы подчеркнуть разницу между результатом asListи нормойArrayList
NIA
73

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

ImmutableList<Integer> values = ImmutableList.of(0, 1, 2, 3);
ColinD
источник
3
+1 Gumm's ImmutableList даже лучше, чем Collections.unmodifiableList, потому что это отдельный тип.
Слеське
29
ImmutableList часто лучше (это зависит от варианта использования), потому что он неизменен. Collections.unmodifiableList не является неизменным. Скорее, это мнение, что приемники не могут измениться, но первоначальный источник МОЖЕТ измениться.
Чарли Коллинз
2
@CharlieCollins, если нет способа доступа к исходному источнику, чем Collections.unmodifiableListдостаточно, чтобы сделать List неизменным?
Саванибхарат
1
@savanibharat Да.
Просто студент
20

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

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

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

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

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

Йоахим Зауэр
источник
3
Где он упомянул, что ему нужен добытчик?
Эрик Робертсон
6
@Erik: он этого не сделал, но это очень распространенный вариант использования неизменяемых структур данных (я изменил ответ, чтобы обратиться к методам в целом, поскольку решение применяется везде, даже если оно чаще встречается в методах получения).
Иоахим Зауэр
7
Это лучше использовать clone()или Arrays.copy()здесь?
Кевинарпе
Насколько я помню, согласно «Эффективной Java» Джошуа Блоха, clone () определенно является предпочтительным способом.
Винсент
1
Последнее предложение пункта 13 «Правильно переопределять клонирование»: «Как правило, функциональность копирования лучше всего предоставляется конструкторами или фабриками. Примечательным исключением из этого правила являются массивы, которые лучше всего копировать с помощью метода клонирования».
Винсент
14

Есть один способ сделать неизменный массив в 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
Brigham
источник
3
Но это не помогает OP получить неизменный массив с данными в нем.
Шридхар Сарнобат
1
@ Шридхар-Сарнобат. В этом суть ответа. В Java нет способа создать неизменный массив с данными в нем.
Бригам
1
Мне больше нравится этот более простой синтаксис: private static final String [] EMPTY_STRING_ARRAY = {}; (Очевидно, я не разобрался, как сделать «мини-разметку». Кнопка предварительного просмотра была бы хороша.)
Wes
6

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

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"});
}
aeracode
источник
какая польза от копирования этого массива в конструкторе, поскольку мы не предоставили никакого метода установки?
Виджая Кумар
Содержимое входного массива все еще может быть изменено позже. Мы хотим сохранить исходное состояние, поэтому копируем его.
Aeracode
4

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

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

rmuller
источник
3

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

Томас Мюллер
источник
3

Начиная с 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.

Жан Вальжан
источник
1

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

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

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


источник
4
Не нужно писать все это valueOf(), об этом позаботится автобокс. Также Arrays.asList(0, 2, 3, 4)было бы гораздо более кратким.
Иоахим Зауэр
@Joachim: Смысл valueOf()использования внутреннего кеша объектов Integer для уменьшения потребления / перезапуска памяти.
Esko
4
@Esko: прочитайте спецификацию автобокса. Он делает то же самое, поэтому здесь нет никакой разницы.
Иоахим Зауэр
1
@John Вы никогда не получите NPE, конвертирующую intв Integerхотя; просто нужно быть осторожным, наоборот.
ColinD
1
@Джон: ты прав, это может быть опасно. Но вместо того, чтобы избегать этого полностью, вероятно, лучше понять опасность и избежать этого.
Иоахим Зауэр
1

В некоторых ситуациях будет проще использовать этот статический метод из библиотеки 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})
kevinarpe
источник
1

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

Отображаемое имя
источник
1

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

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

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

String[] array = ... ;
List<String[]> list = List.<String[]>of(array);
akhil_mittal
источник
0

Хотя это правда, что 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()));

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

miguelt
источник
-3

Ну .. массивы полезно передавать как константы (если они были) как параметры вариантов.

Мартин
источник
Что такое «параметр вариантов»? Никогда об этом не слышал.
Слеське
2
Использование массивов в качестве констант - именно то, что многие люди терпят неудачу. Ссылка постоянная, но содержимое массива изменчиво. Один абонент / клиент может изменить содержимое вашей «константы». Это, вероятно, не то, что вы хотите разрешить. Используйте ImmutableList.
Чарли Коллинз
@CharlieCollins даже это можно взломать с помощью отражения. Ява небезопасен с точки зрения изменчивости ... и это не изменится.
Отображаемое имя
2
@SargeBorsch Это правда, но любой, кто делает взлом на основе рефлексии, должен знать, что он играет с огнем, изменяя вещи, к которым издатель API, возможно, не хотел, чтобы они обращались. Принимая во внимание, что, если API возвращает int[], вызывающая сторона вполне может предположить, что они могут делать с этим массивом то, что они хотят, не затрагивая внутреннюю часть API.
daiscog