Почему я получаю 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

Pentium10
источник
используйте LinkedList.
Лова Читтумури

Ответы:

1007

Довольно много проблем с вашим кодом:

При Arrays.asListвозврате списка фиксированного размера

Из API:

Arrays.asListВозвращает список фиксированного размера, поддерживаемый указанным массивом.

Вы не можете addк этому; ты не можешь removeот этого. Вы не можете структурно изменить List.

Fix

Создайте LinkedList, который поддерживает быстрее remove.

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

О splitпринятии регулярных выражений

Из API:

String.split(String regex): Разбивает эту строку вокруг совпадений заданного регулярного выражения .

|является метасимволом регулярных выражений; если вы хотите разделить литерал |, вы должны экранировать его \|, как строковый литерал Java "\\|".

Fix:

template.split("\\|")

По лучшему алгоритму

Вместо того, чтобы вызывать по removeодному со случайными индексами, лучше генерировать достаточно случайных чисел в диапазоне, а затем обходить Listодин раз с помощью a listIterator(), вызывая remove()соответствующие индексы. Есть вопросы о потоке стека о том, как генерировать случайные, но разные числа в заданном диапазоне.

С этим ваш алгоритм будет O(N).

polygenelubricants
источник
Спасибо, у меня есть только ограниченные элементы в строке <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, но похоже, что это то же самое для Android Java.

РЕДАКТИРОВАТЬ

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

Андреас Долк
источник
4

Arrays.asList () возвращает список, который не допускает операций, влияющих на его размер (обратите внимание, что это не то же самое, что «неизменяемый»).

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

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

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

Обновление 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 за алгоритм, хотя OP говорит, что всего 10 элементов. И хороший способ использования случайных чисел с ArrayList. Гораздо проще, чем мое предложение. Я думаю, что это приведет к переупорядочению элементов.
полигенасмазочные материалы
4

У меня есть другое решение этой проблемы:

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

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

ZZ 5
источник
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
Добро пожаловать в ТАК. Хотя мы благодарим вас за ваш ответ, было бы лучше, если бы он предоставил дополнительную ценность поверх других ответов. В этом случае ваш ответ не дает дополнительной ценности, поскольку другой пользователь уже опубликовал это решение. Если предыдущий ответ был вам полезен, вы должны проголосовать за него, как только у вас появится достаточно репутации.
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 вызывается, он возвращает список своих собственных версий статического класса, который не переопределяет функцию add из 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

Venkat
источник
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);
Бхагьяшри Нигаде
источник