Warum erhalte ich eine UnsupportedOperationException, wenn ich versuche, ein Element aus einer Liste zu entfernen?

476

Ich habe diesen Code:

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

Ich verstehe das:

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)

Wie wäre das der richtige Weg? Java.15

Pentium10
quelle
Verwenden Sie LinkedList.
Lova Chittumuri

Antworten:

1006

Einige Probleme mit Ihrem Code:

Bei Arrays.asListRückgabe einer Liste mit fester Größe

Aus der API:

Arrays.asList: Gibt eine Liste mit fester Größe zurück, die vom angegebenen Array unterstützt wird.

Sie können nicht adddazu; du kannst nicht removedavon. Sie können das nicht strukturell ändern List.

Fix

Erstellen Sie eine LinkedList, die schneller unterstützt remove.

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

Bei der splitEinnahme von Regex

Aus der API:

String.split(String regex): Teilt diese Zeichenfolge um Übereinstimmungen des angegebenen regulären Ausdrucks .

|ist ein Regex-Metazeichen; Wenn Sie ein Literal aufteilen möchten |, müssen Sie es maskieren \|, was als Java-String-Literal gilt "\\|".

Fix:

template.split("\\|")

Auf besseren Algorithmus

Anstatt einzeln removemit zufälligen Indizes aufzurufen , ist es besser, genügend Zufallszahlen im Bereich zu generieren und diese dann Listeinmal mit a zu durchlaufen listIterator()und remove()entsprechende Indizes aufzurufen . Es gibt Fragen zum Stapelüberlauf, wie zufällige, aber unterschiedliche Zahlen in einem bestimmten Bereich generiert werden können.

Damit wäre Ihr Algorithmus O(N).

Polygenschmierstoffe
quelle
Vielen Dank, ich habe nur begrenzte Elemente in der Zeichenfolge <10, daher ist dies kein Optimierungsproblem.
Pentium10
6
@ Pentium: noch eine Sache: Sie sollten nicht Randomjedes Mal eine neue Instanz erstellen. Machen Sie es zu einem staticFeld und säen Sie es nur einmal.
Polygenelubricants
6
Ist LinkedList wirklich schneller? Sowohl LinkedList als auch ArrayList haben O (n) hier entfernt: \ Es ist fast immer besser, nur eine ArrayList zu verwenden
gengkev
2
LinkedList vs ArrayList -> Es gibt ein Leistungstestdiagramm von Ryan. LinkedList lässt sich schneller entfernen.
Torno
LinkedList nur beim Entfernen wirklich schneller, wenn der zu entfernende Knoten bereits bekannt ist. Wenn Sie versuchen, ein Element zu entfernen, muss die Liste durchlaufen werden, wobei jedes Element verglichen wird, bis das richtige gefunden wird. Wenn Sie versuchen, nach Index zu entfernen, müssen n Durchquerungen durchgeführt werden. Diese Durchläufe sind sehr teuer und der schlimmste Fall für das CPU-Caching: Viele Sprünge auf unvorhersehbare Weise im Speicher. Siehe: youtube.com/watch?v=YQs6IC-vgmo
Alexander - Reinstate Monica
143

Dieser hat mich oft verbrannt. Arrays.asListerstellt eine nicht veränderbare Liste. Aus dem Javadoc: Gibt eine Liste mit fester Größe zurück, die vom angegebenen Array unterstützt wird.

Erstellen Sie eine neue Liste mit demselben Inhalt:

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

Dies wird ein wenig zusätzlichen Müll erzeugen, aber Sie können ihn mutieren.

Nick Orton
quelle
6
Kleiner Punkt, aber Sie "wickeln" die ursprüngliche Liste nicht ein, sondern erstellen eine völlig neue Liste (weshalb dies funktioniert).
Jack Leow
Ja, ich habe Arrays.asList () in meinem JUnit-Testfall verwendet, der dann in meiner Karte gespeichert wurde. Mein Code wurde geändert, um die übergebene Liste in meine eigene ArrayList zu kopieren.
cs94njw
Ihre Lösung funktioniert in meiner Situation nicht, aber danke für die Erklärung. Das von Ihnen bereitgestellte Wissen führte zu meiner Lösung.
Scott Biggs
54

Wahrscheinlich, weil Sie mit arbeiten modifizierbaren Wrapper arbeiten .

Ändern Sie diese Zeile:

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

zu dieser Zeile:

List<String> list = new LinkedList<>(Arrays.asList(split));
römisch
quelle
5
Arrays.asList () ist kein nicht veränderbarer Wrapper.
Dimitris Andreou
@polygenelubricants: Es scheint, Sie verwechseln unmodifiableund immutable. unmodifiablebedeutet genau "veränderbar, aber nicht strukturell".
Roman
2
Ich habe gerade versucht, einen unmodifiableListWrapper zu erstellen und einen set; es wirft UnsupportedOperationException. Ich bin mir ziemlich sicher, dass es Collections.unmodifiable*wirklich volle Unveränderlichkeit bedeutet, nicht nur strukturelle.
Polygenelubricants
1
Wenn ich diese Kommentare 7 Jahre später lese, erlaube ich mir, diesen Link anzugeben : stackoverflow.com/questions/8892350/… wahrscheinlich den Unterschied zwischen unveränderlich und nicht veränderbar zu beheben, der hier diskutiert wird.
Nathan Ripert
14

Ich denke, dass das Ersetzen:

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

mit

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

behebt das Problem.

Salim Hamidi
quelle
5

Die von zurückgegebene Liste ist Arrays.asList()möglicherweise unveränderlich. Könnten Sie es versuchen

List<String> list = new ArrayList(Arrays.asList(split));
Pierre
quelle
1
Er löscht, ArrayList ist nicht die beste Datenstruktur zum Löschen seiner Werte. LinkedList hat viel mehr mit seinem Problem zu tun.
Roman
2
Falsch in Bezug auf die LinkedList. Er greift über den Index zu, sodass LinkedList so viel Zeit aufwenden würde, um ein Element durch Iteration zu finden. In meiner Antwort finden Sie einen besseren Ansatz für die Verwendung einer ArrayList.
Dimitris Andreou
4

Lesen Sie einfach das JavaDoc für die asList-Methode:

Gibt eine {@code List} der Objekte im angegebenen Array zurück. Die Größe der {@code List} kann nicht geändert werden, dh das Hinzufügen und Entfernen wird nicht unterstützt, aber die Elemente können festgelegt werden. Durch das Festlegen eines Elements wird das zugrunde liegende Array geändert.

Dies ist aus Java 6, aber es sieht so aus, als ob es für Android Java dasselbe ist.

BEARBEITEN

Der Typ der resultierenden Liste Arrays.ArrayListist eine private Klasse in Arrays.class. In der Praxis ist es nichts anderes als eine Listenansicht des Arrays, mit dem Sie übergeben haben Arrays.asList. Mit der Konsequenz: Wenn Sie das Array ändern, wird auch die Liste geändert. Und weil ein Array nicht vergrößerbar ist, entfernen und Addierungsoperation müssen nicht mehr unterstützt.

Andreas Dolk
quelle
4

Arrays.asList () gibt eine Liste zurück, die keine Operationen zulässt, die sich auf die Größe auswirken (beachten Sie, dass dies nicht mit "nicht modifizierbar" identisch ist).

Sie können new ArrayList<String>(Arrays.asList(split));eine echte Kopie erstellen, aber wenn Sie sehen, was Sie tun möchten, finden Sie hier einen zusätzlichen Vorschlag (Sie haben eineO(n^2) Algorithmus direkt darunter).

Sie möchten zufällige Elemente aus der Liste entfernen list.size() - count(nennen wir dies k). kWählen Sie einfach so viele zufällige Elemente aus und tauschen Sie sie an die Endpositionen der Liste aus. Löschen Sie dann den gesamten Bereich (z. B. mit subList () und clear ()). Das würde es zu einem schlanken und mittleren O(n)Algorithmus machen ( O(k)ist genauer).

Update : Wie unten erwähnt, ist dieser Algorithmus nur dann sinnvoll, wenn die Elemente ungeordnet sind, z. B. wenn die Liste eine Tasche darstellt. Wenn andererseits die Liste eine aussagekräftige Reihenfolge hat, würde dieser Algorithmus diese nicht beibehalten (der Algorithmus der Polygenschmierstoffe würde dies stattdessen tun).

Update 2 : Rückblickend wäre ein besserer Algorithmus (linear, Beibehaltung der Reihenfolge, aber mit O (n) Zufallszahlen) ungefähr so:

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--;
  }
}
Dimitris Andreou
quelle
1
+1 für den Algorithmus, obwohl OP sagt, dass es nur 10 Elemente gibt. Und schöne Art, die Zufallszahlen mit zu verwenden ArrayList. Viel einfacher als mein Vorschlag. Ich denke, es würde jedoch zu einer Neuordnung der Elemente führen.
Polygenelubricants
4

Ich habe eine andere Lösung für dieses Problem:

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

Arbeit an newList;)

ZZ 5
quelle
2

Diese UnsupportedOperationException tritt auf, wenn Sie versuchen, eine Operation für die Sammlung auszuführen, bei der dies nicht zulässig ist, und in Ihrem Fall, wenn Sie sie aufrufen Arrays.asList, wird a nicht zurückgegeben java.util.ArrayList. Es java.util.Arrays$ArrayListwird eine unveränderliche Liste zurückgegeben. Sie können es nicht hinzufügen und nicht entfernen.

Mayank Gupta
quelle
2

Ja, weiter Arrays.asList wird eine Liste mit fester Größe zurückgegeben.

Verwenden Sie einfach eine verknüpfte Liste addAll Methodenliste.

Beispiel:

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

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

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

parentRecepeIdList.add("555");
Sameer Kazi
quelle
2

Ersetzen

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

zu

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

oder

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

oder

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

oder (Besser zum Entfernen von Elementen)

List<String> list = new LinkedList<>(Arrays.asList(split));
Karthik Kompelli
quelle
2

Arraylist narraylist = Arrays.asList (); // Unveränderliche Arrayliste zurückgeben Um sie veränderlich zu machen, wäre die Lösung: Arraylist narraylist = new ArrayList (Arrays.asList ());

Bruce Wayne
quelle
1
Willkommen bei SO. Obwohl wir uns für Ihre Antwort bedanken, wäre es besser, wenn sie zusätzlich zu den anderen Antworten einen zusätzlichen Wert liefern würde. In diesem Fall bietet Ihre Antwort keinen zusätzlichen Wert, da ein anderer Benutzer diese Lösung bereits veröffentlicht hat. Wenn eine vorherige Antwort für Sie hilfreich war, sollten Sie sie abstimmen, sobald Sie einen ausreichenden Ruf haben.
technogeek1995
1

Es folgt ein Codeausschnitt aus Arrays

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;

Wenn also die asList-Methode aufgerufen wird, gibt sie eine Liste ihrer eigenen privaten statischen Klassenversion zurück, die die Add-Funktion von AbstractList zum Speichern des Elements im Array nicht überschreibt. Standardmäßig löst die Methode add in der abstrakten Liste eine Ausnahme aus.

Es handelt sich also nicht um eine reguläre Array-Liste.

Gagandeep Singh
quelle
1

Sie können keine Arrays mit fester Größe entfernen oder zu einer Liste mit fester Größe hinzufügen.

Sie können jedoch Ihre Unterliste aus dieser Liste erstellen.

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

* Anderer Weg ist

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

Dadurch wird eine ArrayList erstellt, deren Größe nicht wie bei Arrays.asList festgelegt ist

Venkat
quelle
0

Arrays.asList() verwendet intern ein Array mit fester Größe.
Sie können dies nicht dynamisch hinzufügen oder entfernenArrays.asList()

Benutze das

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

In können narraylistSie einfach Elemente hinzufügen oder entfernen.

Roushan Kumar
quelle
0

Das Erstellen einer neuen Liste und das Auffüllen gültiger Werte in eine neue Liste hat bei mir funktioniert.

Code-Wurffehler -

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

Nach dem Fix -

 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);
Bhagyashree Nigade
quelle