Незмінний масив на 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: так, але вони сильно стримують; найкраще ввійти в звичку думати "чи мені справді потрібен масив чи можу я замість цього використовувати список?"
Jason S

7
@Colin: Оптимізуйте лише тоді, коли вам потрібно. Коли ви виявите, що витрачаєте півхвилини на додавання чогось, що, наприклад, збільшує масив або ви тримаєте якийсь індекс поза межами циклу for-циклу, ви вже витратили частину часу свого начальника. Створіть спочатку, оптимізуйте, коли і де це потрібно - і в більшості програм це не в заміні списків масивами.
fwielstra

9
Ну чому б ніхто не згадав int[]і new int[]чи набагато легше набрати, ніж List<Integer>і new ArrayList<Integer>? XD
lcn

Відповіді:


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/… "Повертає список фіксованого розміру, підкріплений вказаним масивом. (Зміни до повернутого списку" записувати через "в масив.)"
Джейсон S

7
@JasonS добре, Arrays.asList()насправді здається незмінним у тому сенсі, що ви не можете addабо removeелементи повернутого java.util.Arrays.ArrayList( не плутати з цим java.util.ArrayList) - ці операції просто не здійснюються. Може, @mauhiz намагається сказати це? Але, звичайно, ви можете змінювати наявні елементи 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);

3
+1 ImmutableList Guava навіть краще, ніж Collections.unmodifiableList, оскільки це окремий тип.
sleske

29
ImmutableList часто краще (це залежить від випадку використання), тому що він незмінний. Collections.unmodifiableList не є незмінним. Швидше, це уявлення, що приймачі не можуть змінюватися, але вихідне джерело МОЖЕ змінитися.
Чарлі Коллінз

2
@CharlieCollins, якщо немає ніякого способу отримати доступ до оригінального джерела, ніж це Collections.unmodifiableListдостатньо для того, щоб список не змінювався?
savanibharat

1
@savanibharat Так.
Просто студент

20

Як зазначали інші, ви не можете мати незмінних масивів на Java.

Якщо вам абсолютно потрібен метод, який повертає масив, який не впливає на початковий масив, вам потрібно буде клонувати масив кожен раз:

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

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

Ця техніка називається виготовленням оборонної копії.


3
Де він згадав, що йому потрібен геттер?
Ерік Робертсон

6
@Erik: він цього не зробив, але це дуже поширений випадок використання незмінних структур даних (я змінив відповідь на посилання на методи в цілому, оскільки рішення застосовується скрізь, навіть якщо воно частіше зустрічається у гетерів).
Йоахім Зауер

7
Краще використовувати clone()чи Arrays.copy()тут?
kevinarpe

Наскільки я пам’ятаю, згідно з «Ефективною Явою» Джошуа Блоха, клон (), безумовно, є кращим способом.
Вінсент

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

3
Але це не допомагає ОП досягти незмінного масиву з даними в ньому.
Шрідхар Сарнобат

1
@ Шрідхар-Сарнобат У цьому полягає суть відповіді. У Java немає можливості зробити незмінний масив з даними в ньому.
Брігам

1
Мені подобається цей простіший синтаксис: приватний статичний остаточний рядок [] EMPTY_STRING_ARRAY = {}; (Очевидно, я не зрозумів, як зробити "міні-відмітку". Кнопка попереднього перегляду була б непоганою.)
Уес

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

в чому полягає користь копіювання цього масиву в конструктор, оскільки ми не запропонували жодного методу встановлення?
vijaya kumar

Вміст вхідного масиву все ще може бути змінено пізніше. Ми хочемо зберегти його первісний стан, тому копіюємо його.
аерокод

4

На Java-ви можете використовувати List.of(...), JavaDoc .

Цей метод повертає непорушний Listта дуже ефективний.


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 для зменшення споживання / переробки пам'яті.
Есько

4
@Esko: прочитати специфікацію автобоксингу. Це робить саме те саме, тому різниці тут немає.
Йоахім Зауер

1
@John Ви ніколи не отримаєте NPE, який перетворює intна, Integerхоча; просто треба бути обережними навпаки.
ColinD

1
@John: ти маєш рацію, це може бути небезпечно. Але замість того, щоб уникнути цього повністю, можливо, краще зрозуміти небезпеку та уникати цього.
Йоахім Зауер

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

1

Якщо ви хочете уникнути як незмінності, так і боксу, виходу з коробки немає. Але ви можете створити клас, який містить примітивний масив всередині та забезпечує доступ лише до читання елементів за допомогою методів.


1

Метод (E ... елементів) в Java9 може бути використаний для створення непорушного списку, використовуючи лише рядок:

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

Вищеописаний метод повертає незмінний список, що містить довільну кількість елементів. І додавання будь-якого цілого числа до цього списку призведе до java.lang.UnsupportedOperationExceptionвиключення. Цей метод також приймає один аргумент як аргумент.

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

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

Не ідеально, але принаймні у вас є "псевдозмінні масиви" (з точки зору класу), і це не порушить пов'язаний код.


-3

Ну .. масиви корисно передавати як константи (якщо вони були) як параметри варіантів.


Що таке "параметр варіантів"? Ніколи не чув про це.
sleske

2
Використання масивів як констант саме там , де багато людей не вдається. Посилання є постійним, але вміст масиву може змінюватися. Один абонент / клієнт може змінити вміст вашого "постійного". Це, мабуть, не те, що ви хочете дозволити. Використовуйте ImmutableList.
Чарлі Коллінз

@CharlieCollins навіть це можна зламати через рефлексію. Java - небезпечна мова з точки зору змін, і це не зміниться.
Відобразити ім’я

2
@SargeBorsch Це правда, але кожен, хто робить злом на основі роздумів, повинен знати, що вони грають з вогнем, змінюючи речі, до яких видавець API, можливо, не хотів, щоб вони отримали доступ. Оскільки, якщо API повертає int[], абонент може припустити, що вони можуть робити те, що хочуть, з цим масивом, не впливаючи на внутрішні дані API.
дайског
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.