Dlaczego otrzymuję wyjątek UnsupportedOperationException podczas próby usunięcia elementu z listy?


476

Mam ten kod:

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

Rozumiem:

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)

Jak by to było poprawne? Java.15


użyj LinkedList.
Lova Chittumuri

Odpowiedzi:


1006

Sporo problemów z twoim kodem:

Po Arrays.asListzwróceniu listy o stałym rozmiarze

Z interfejsu API:

Arrays.asList: Zwraca listę o stałym rozmiarze, popartą określoną tablicą.

Nie możesz tego addzrobić; nie możesz removez tego. Nie możesz modyfikować strukturalnie List.

Naprawić

Utwórz LinkedList, który obsługuje szybciej remove.

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

Po splitprzyjęciu wyrażenia regularnego

Z interfejsu API:

String.split(String regex): Dzieli ten ciąg wokół dopasowań podanego wyrażenia regularnego .

|jest metaznakiem wyrażenia regularnego; jeśli chcesz podzielić na literał |, musisz uciec od niego \|, który jest literałem łańcuchowym Java "\\|".

Naprawić:

template.split("\\|")

Na lepszy algorytm

Zamiast dzwonić removepojedynczo za pomocą losowych indeksów, lepiej jest wygenerować wystarczającą liczbę liczb losowych w zakresie, a następnie przejść Listraz za pomocą a listIterator(), sprawdzając remove()odpowiednie wskaźniki. Istnieją pytania dotyczące przepływu stosu, jak generować losowe, ale odrębne liczby w danym zakresie.

Dzięki temu twój algorytm byłby O(N).


Dzięki, mam tylko ograniczone elementy w ciągu <10, więc nie będzie problemu z optymalizacją.
Pentium10

6
@ Pentium: jeszcze jedno: nie powinieneś tworzyć nowej instancji Randomza każdym razem. Zrób to staticpole i zasiej je tylko raz.
polygenelubricants

6
Czy LinkedList jest naprawdę szybszy? Zarówno LinkedList, jak i ArrayList mają O (n) do usunięcia tutaj: \ Prawie zawsze lepiej jest po prostu użyć ArrayList
gengkev

2
LinkedList vs ArrayList -> Istnieje wykres testu wydajności Ryana. LinkedList jest szybszy w usuwaniu.
torno

LinkedList jest bardzo szybszy przy usuwaniu, gdy węzeł do usunięcia jest już znany. Jeśli próbujesz usunąć element, lista musi być przeglądana, a każdy element porównywany do momentu znalezienia właściwego. Jeśli próbujesz usunąć według indeksu, musisz wykonać n podróży. Te przejścia są bardzo drogie i najgorszy przypadek buforowania procesora: wiele przeskakuje pamięć w nieprzewidywalny sposób. Zobacz: youtube.com/watch?v=YQs6IC-vgmo
Alexander - Przywróć Monikę

143

Ten spalił mnie wiele razy. Arrays.asListtworzy listę niemodyfikowalną. Z Javadoc: Zwraca listę o stałym rozmiarze wspieraną przez określoną tablicę.

Utwórz nową listę o tej samej treści:

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

Spowoduje to wytworzenie dodatkowego śmieci, ale będziesz mógł je mutować.


6
Drobny punkt, ale nie „zawijasz” oryginalnej listy, tworzysz zupełnie nową listę (dlatego działa).
Jack Leow

Tak, użyłem Arrays.asList () w moim przypadku testowym JUnit, który następnie został zapisany w mojej mapie. Zmieniono mój kod, aby skopiować przekazaną listę do mojej własnej tablicy ArrayList.
cs94njw

Twoje rozwiązanie nie działa w mojej sytuacji, ale dziękuję za wyjaśnienie. Wiedza, którą dostarczyłeś, doprowadziła do mojego rozwiązania.
Scott Biggs

54

Prawdopodobnie dlatego, że pracujesz z niemodyfikowalnym opakowaniem .

Zmień tę linię:

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

do tej linii:

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

5
Arrays.asList () nie jest niezmodyfikowanym opakowaniem.
Dimitris Andreou

@polygenelubricants: wygląda na to, że się pomieszałeś unmodifiablei immutable. unmodifiableoznacza dokładnie „modyfikowalny, ale nie strukturalnie”.
Roman

2
Właśnie próbowałem utworzyć unmodifiableListopakowanie i spróbować set; rzuca UnsupportedOperationException. Jestem całkiem pewien, Collections.unmodifiable*że naprawdę oznacza pełną niezmienność, a nie tylko konstrukcję.
polygenelubricants

1
Czytając te komentarze 7 lat później, pozwalam sobie wskazać ten link: stackoverflow.com/questions/8892350/... prawdopodobnie naprawi różnicę między niezmiennymi a niemodyfikowalnymi, omówionymi tutaj.
Nathan Ripert

14

Myślę, że zastępując:

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

z

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

rozwiązuje problem.


5

Lista zwrócona przez Arrays.asList()może być niezmienna. Mógłbyś spróbować

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

1
usuwa, ArrayList nie jest najlepszą strukturą danych do usuwania jego wartości. LinkedList ma o wiele więcej za swój problem.
Roman

2
Błędne w odniesieniu do LinkedList. Ma dostęp do indeksu, więc LinkedList poświęciłby tyle samo czasu na znalezienie elementu poprzez iterację. Zobacz moją odpowiedź na lepsze podejście, korzystając z ArrayList.
Dimitris Andreou

4

Po prostu przeczytaj JavaDoc dla metody asList:

Zwraca {@ lista kodów} obiektów w określonej tablicy. Rozmiar {@code List} nie może być modyfikowany, tzn. Dodawanie i usuwanie nie jest obsługiwane, ale elementy można ustawić. Ustawienie elementu modyfikuje podstawową tablicę.

To jest z Java 6, ale wygląda na to, że tak samo jest z Androidem Java.

EDYTOWAĆ

Typ wynikowej listy to Arrays.ArrayListprywatna klasa Arrays.class. Praktycznie rzecz biorąc, jest to tylko widok listy w tablicy, którą przekazałeś Arrays.asList. W konsekwencji: jeśli zmienisz tablicę, lista również się zmieni. A ponieważ rozmiaru tablicy nie można zmienić, operacja usuwania i dodawania musi być nieobsługiwana.


4

Arrays.asList () zwraca listę, która nie zezwala na operacje wpływające na jej rozmiar (zwróć uwagę, że nie jest to to samo co „niemodyfikowalne”).

Możesz zrobić, new ArrayList<String>(Arrays.asList(split));aby stworzyć prawdziwą kopię, ale widząc, co próbujesz zrobić, oto dodatkowa sugestia (maszO(n^2) algorytm poniżej).

Chcesz usunąć list.size() - count(nazwijmy to k) losowe elementy z listy. Po prostu wybierz tyle losowych elementów i zamień je na końcowe kpozycje listy, a następnie usuń cały zakres (np. Używając subList () i clear ()). To zmieniłoby go w uproszczony i średni O(n)algorytm ( O(k)jest bardziej precyzyjny).

Aktualizacja : Jak zauważono poniżej, ten algorytm ma sens tylko wtedy, gdy elementy są nieuporządkowane, np. Jeśli lista reprezentuje torbę. Z drugiej strony, jeśli lista ma znaczącą kolejność, algorytm nie zachowałby jej (zamiast tego algorytm wieloskładnikowy).

Aktualizacja 2 : Z perspektywy czasu lepszy (liniowy, zachowujący porządek, ale z liczbami losowymi O (n)) byłby mniej więcej taki:

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 dla algorytmu, choć OP mówi, że jest tylko 10 elementów. I fajny sposób na użycie liczb losowych z ArrayList. O wiele prostsze niż moja sugestia. Myślę jednak, że spowodowałoby to zmianę kolejności elementów.
polygenelubricants

4

Mam inne rozwiązanie tego problemu:

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

pracować nad newList;)


2

Ten wyjątek UnsupportedOperationException pojawia się, gdy próbujesz wykonać pewne operacje na zbiorze, w których jest to niedozwolone, aw twoim przypadku, gdy zadzwonisz Arrays.asList, nie zwraca a java.util.ArrayList. Zwraca java.util.Arrays$ArrayListlistę niezmienną. Nie możesz go dodać i nie możesz go usunąć.


2

Tak, wł Arrays.asList , zwracanie listy o stałym rozmiarze.

Inne niż użycie listy połączonej, po prostu użyj addAll listy metod.

Przykład:

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

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

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

parentRecepeIdList.add("555");

2

Zastąpić

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

do

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

lub

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

lub

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

lub (Lepiej usuń elementy)

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

2

Arraylist narraylist = Arrays.asList (); // Zwraca niezmienną listę arraylistyczną Aby zmienne rozwiązanie było możliwe: Arraylist narraylist = new ArrayList (Arrays.asList ());


1
Witamy w SO. Chociaż dziękujemy za odpowiedź, byłoby lepiej, gdyby stanowiła dodatkową wartość oprócz innych odpowiedzi. W takim przypadku Twoja odpowiedź nie zapewnia dodatkowej wartości, ponieważ inny użytkownik już opublikował to rozwiązanie. Jeśli poprzednia odpowiedź była dla ciebie pomocna, powinieneś zagłosować, gdy będziesz mieć wystarczającą reputację.
technogeek1995

1

Poniżej znajduje się fragment kodu z tablic

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;

więc co się dzieje, gdy wywoływana jest metoda asList, wówczas zwraca listę swojej prywatnej wersji klasy statycznej, która nie zastępuje dodawania funkcji z AbstractList do przechowywania elementu w tablicy. Więc domyślnie metoda dodawania w liście abstrakcyjnej zgłasza wyjątek.

Nie jest to więc zwykła lista tablic.


1

Nie można usunąć ani dodać do listy tablic o stałym rozmiarze.

Ale możesz utworzyć swoją listę podrzędną z tej listy.

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

* Innym sposobem jest

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

spowoduje to utworzenie ArrayList, który nie ma ustalonego rozmiaru, jak Arrays.asList


0

Arrays.asList() korzysta wewnętrznie z tablicy o stałym rozmiarze.
Nie możesz dynamicznie dodawać ani usuwać z tegoArrays.asList()

Użyj tego

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

W narraylistmożesz łatwo dodawać lub usuwać elementy.


0

Utworzenie nowej listy i wypełnienie prawidłowych wartości na nowej liście działało dla mnie.

Błąd generowania kodu -

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

Po naprawie -

 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);
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.