Чому я отримую UnsupportedOperationException при спробі видалити елемент зі списку?


476

У мене є цей код:

public static String SelectRandomFromTemplate(String template,int count) {
   String[] split = template.split("|");
   List<String> list=Arrays.asList(split);
   Random r = new Random();
   while( list.size() > count ) {
      list.remove(r.nextInt(list.size()));
   }
   return StringUtils.join(list, ", ");
}

Я отримую це:

06-03 15:05:29.614: ERROR/AndroidRuntime(7737): java.lang.UnsupportedOperationException
06-03 15:05:29.614: ERROR/AndroidRuntime(7737):     at java.util.AbstractList.remove(AbstractList.java:645)

Як би це було правильно? Java.15


використовувати LinkedList.
Lova Chittumuri

Відповіді:


1006

Досить кілька проблем із кодом:

Після Arrays.asListповернення списку фіксованого розміру

З API:

Arrays.asList: Повертає список фіксованого розміру, підкріплений вказаним масивом.

Ви не можете addдо цього; ти не можеш removeвід цього. Ви не можете структурно змінити List.

Виправити

Створіть LinkedList, який підтримує швидше remove.

List<String> list = new LinkedList<String>(Arrays.asList(split));

Про splitприйом регексу

З API:

String.split(String regex): Розбиває цей рядок навколо збігів заданого регулярного виразу .

|- метахарактер регулярних виразів; якщо ви хочете розділити на літерал |, вам слід відмовитися від нього \|, що є літеральним рядком Java "\\|".

Виправити:

template.split("\\|")

Про кращий алгоритм

Замість того, щоб телефонувати по removeчерзі випадковими індексами, краще генерувати достатньо випадкових чисел у діапазоні, а потім обходити Listодин раз за допомогою A listIterator(), викликаючи remove()відповідні індекси. Є питання про stackoverflow про те, як генерувати випадкові, але чіткі числа в заданому діапазоні.

При цьому ваш алгоритм був би O(N).


Дякую, у мене є лише обмежені елементи в рядку <10, тому не буде проблеми з оптимізацією.
Pentium10

6
@Pentium: ще одне: вам не слід створювати новий примірник Randomкожного разу. Зробіть це staticполе і посіяйте його лише один раз.
полігенмастильні матеріали

6
Чи дійсно LinkedList швидше? І LinkedList, і ArrayList мають O (n) видалити тут: \ Майже завжди краще просто скористатися ArrayList
gengkev

2
LinkedList vs ArrayList -> Існує графік тесту на ефективність від Райана. LinkedList швидше видаляє.
торно

LinkedList тільки швидше видаляється, коли вузол, який потрібно видалити, вже відомий. Якщо ви намагаєтесь видалити елемент, список потрібно пройти, при цьому кожен елемент порівнюється, поки не буде знайдений правильний. Якщо ви намагаєтесь видалити за індексом, необхідно виконати n обходу. Ці траверси коштують надто дорого, і найгірший випадок кешування процесора: багато стрибає навколо пам’яті непередбачуваними способами. Дивіться: youtube.com/watch?v=YQs6IC-vgmo
Олександр - Відновіть Моніку

143

Цей мене багато разів спалював. Arrays.asListстворює немодифікується список. З Javadoc: Повертає список фіксованого розміру, підкріплений вказаним масивом.

Створіть новий список з тим самим вмістом:

newList.addAll(Arrays.asList(newArray));

Це створить трохи зайвого сміття, але ви зможете його вимкнути.


6
Незначна точка, але ви не "обгортаєте" початковий список, ви створюєте абсолютно новий список (саме тому він працює).
Джек Лев

Так, я використовував Arrays.asList () у своєму тестовому випадку JUnit, який потім зберігався у моїй карті. Змінив свій код, щоб скопіювати переданий список, у свій власний ArrayList.
cs94njw

Ваше рішення не працює в моїй ситуації, але дякую за пояснення. Надані вами знання призвели до мого рішення.
Скотт Біггс

54

Можливо, тому, що ви працюєте з немодифікованою обгорткою .

Змініть цей рядок:

List<String> list = Arrays.asList(split);

до цього рядка:

List<String> list = new LinkedList<>(Arrays.asList(split));

5
Arrays.asList () не є немодифікованою обгорткою.
Димитріс Андреу

@polygenelubricants: здається, ви змішуєте unmodifiableі immutable. unmodifiableозначає точно "модифікований, але не структурно".
Роман

2
Я просто спробував створити unmodifiableListобгортку і спробувати set; це кидає UnsupportedOperationException. Я цілком певний, що Collections.unmodifiable*насправді означає повну незмінність, а не лише структурну.
полігенмастильні матеріали

1
Читаючи ці коментарі через 7 років, я дозволяю собі вказати на це посилання: stackoverflow.com/questions/8892350/… ймовірно, щоб виправити різницю між незмінним та немодифікуючим, обговорюваним тут.
Натан Ріперт

14

Я думаю, що замінивши:

List<String> list = Arrays.asList(split);

з

List<String> list = new ArrayList<String>(Arrays.asList(split));

вирішує проблему.


5

Список, що повертається, Arrays.asList()може бути незмінним. Чи можете ви спробувати

List<String> list = new ArrayList(Arrays.asList(split));

1
він видаляє, ArrayList - не найкраща структура даних для видалення його значень. LinkedList подає набагато більше за свою проблему.
Роман

2
Неправильно щодо LinkedList. Він отримує доступ до індексу, тому LinkedList витратить стільки часу, щоб знайти елемент за допомогою ітерації. Дивіться мою відповідь щодо кращого підходу, використовуючи ArrayList.
Димитріс Андреу

4

Просто прочитайте JavaDoc для методу asList:

Повертає {@code List} об’єктів у вказаному масиві. Розмір {@code List} неможливо змінити, тобто додавання та видалення не підтримуються, але елементи можна встановити. Встановлення елемента змінює базовий масив.

Це з Java 6, але схоже, що це саме для андроїд-Java.

EDIT

Тип результуючого списку - Arrays.ArrayListце приватний клас всередині Arrays.class. Практично кажучи, це не що інше, як перегляд списку на масиві, який ви пройшли Arrays.asList. З наслідком: якщо ви зміните масив, список також буде змінено. А оскільки масив не може бути змінено величиною, операція видалення та додавання повинна не підтримуватися.


4

Arrays.asList () повертає список, який не дозволяє операціям, що впливають на його розмір (зауважте, що це не те саме, що "немодифікується").

Можна new ArrayList<String>(Arrays.asList(split));створити справжню копію, але побачивши, що ви намагаєтеся зробити, ось додаткова пропозиція (у вас є O(n^2)алгоритм прямо під цим).

Ви хочете видалити list.size() - count(дозволить назвати це k) випадкові елементи зі списку. Просто виберіть стільки випадкових елементів і поміняйте їх на кінцеві kпозиції списку, а потім видаліть цілий діапазон (наприклад, використовуючи subList () та очистіть () на цьому). Це перетворило б його на негідний і середній O(n)алгоритм ( O(k)точніше).

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

Оновлення 2 : Отже, в ретроспективі, кращий (лінійний, підтримуючи порядок, але з O (n) випадковими числами) алгоритм буде приблизно таким:

LinkedList<String> elements = ...; //to avoid the slow ArrayList.remove()
int k = elements.size() - count; //elements to select/delete
int remaining = elements.size(); //elements remaining to be iterated
for (Iterator i = elements.iterator(); k > 0 && i.hasNext(); remaining--) {
  i.next();
  if (random.nextInt(remaining) < k) {
     //or (random.nextDouble() < (double)k/remaining)
     i.remove();
     k--;
  }
}

1
+1 для алгоритму, хоча ОП говорить, що всього 10 елементів. І приємний спосіб використання випадкових чисел за допомогою ArrayList. Набагато простіше, ніж моя пропозиція. Я думаю, що це призведе до переупорядкування елементів.
полігенмастильні матеріали

4

У мене є ще одне рішення цієї проблеми:

List<String> list = Arrays.asList(split);
List<String> newList = new ArrayList<>(list);

робота над newList;)


2

Це UnsupportedOperationException виникає, коли ви намагаєтеся виконати якусь операцію над колекцією, де її не дозволено, і у вашому випадку, коли ви викликаєте Arrays.asListїї, не повертається a java.util.ArrayList. Він повертає java.util.Arrays$ArrayListсписок, який є незмінним списком. Ви не можете додати його до нього і ви не можете його видалити.


2

Так, Arrays.asListповернення списку фіксованого розміру.

Крім використання зв'язаного списку, просто використовуйте addAllсписок методів.

Приклад:

String idList = "123,222,333,444";

List<String> parentRecepeIdList = new ArrayList<String>();

parentRecepeIdList.addAll(Arrays.asList(idList.split(","))); 

parentRecepeIdList.add("555");

2

Замініть

List<String> list=Arrays.asList(split);

до

List<String> list = New ArrayList<>();
list.addAll(Arrays.asList(split));

або

List<String> list = new ArrayList<>(Arrays.asList(split));

або

List<String> list = new ArrayList<String>(Arrays.asList(split));

або (Краще для видалення елементів)

List<String> list = new LinkedList<>(Arrays.asList(split));

2

Arraylist narraylist = Arrays.asList (); // Повертає незмінний arraylist Щоб зробити його мутаційним рішенням було б: Arraylist narraylist = new ArrayList (Arrays.asList ());


1
Ласкаво просимо до SO. Хоча ми дякуємо вам за вашу відповідь, було б краще, якби вона надала додаткову цінність поверх інших відповідей. У цьому випадку ваша відповідь не надає додаткового значення, оскільки інший користувач вже розмістив це рішення. Якщо попередня відповідь була для вас корисною, ви повинні проголосувати за неї, як тільки у вас буде достатня репутація.
technogeek1995

1

Далі йде фрагмент коду з масивів

public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a);
    }

    /**
     * @serial include
     */
    private static class ArrayList<E> extends AbstractList<E>
        implements RandomAccess, java.io.Serializable
    {
        private static final long serialVersionUID = -2764017481108945198L;
        private final E[] a;

тож трапляється так, що коли метод виклику asList тоді він повертає список власної приватної версії статичного класу, яка не замінює додавання функції Funcion з AbstractList для зберігання елемента в масиві. Тож за замовчуванням метод add в абстрактному списку кидає виняток.

Тож це не регулярний список масивів.


1

Ви не можете видалити та не можете додати до списку масивів фіксованого розміру.

Але ви можете створити свій список із цього списку.

list = list.subList(0, list.size() - (list.size() - count));

public static String SelectRandomFromTemplate(String template, int count) {
   String[] split = template.split("\\|");
   List<String> list = Arrays.asList(split);
   Random r = new Random();
   while( list.size() > count ) {
      list = list.subList(0, list.size() - (list.size() - count));
   }
   return StringUtils.join(list, ", ");
}

* Інший спосіб є

ArrayList<String> al = new ArrayList<String>(Arrays.asList(template));

це створить ArrayList, який не має фіксованого розміру, як Arrays.asList


0

Arrays.asList() використовує масив фіксованого розміру внутрішньо.
Ви не можете динамічно додавати або видаляти цеArrays.asList()

Використовуй це

Arraylist<String> narraylist=new ArrayList(Arrays.asList());

У narraylistвас можна легко додавати або видаляти елементи.


0

Створення нового списку та заповнення дійсних значень у новому списку працювало для мене.

Помилка вкидання коду -

List<String> list = new ArrayList<>();
   for (String s: list) {
     if(s is null or blank) {
        list.remove(s);
     }
   }
desiredObject.setValue(list);

Після виправлення -

 List<String> list = new ArrayList<>();
 List<String> newList= new ArrayList<>();
 for (String s: list) {
   if(s is null or blank) {
      continue;
   }
   newList.add(s);
 }
 desiredObject.setValue(newList);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.