Pourquoi est-ce que j'obtiens une exception UnsupportedOperationException lorsque j'essaie de supprimer un élément d'une liste?

476

J'ai ce 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, ", ");
}

J'ai compris:

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)

Comment serait-ce la bonne façon? Java.15

Pentium10
la source
utilisez LinkedList.
Lova Chittumuri

Réponses:

1007

Pas mal de problèmes avec votre code:

Au Arrays.asListretour d'une liste de taille fixe

Depuis l'API:

Arrays.asList: Renvoie une liste de taille fixe soutenue par le tableau spécifié.

Vous ne pouvez pas le addfaire; vous ne pouvez pas en removesortir. Vous ne pouvez pas modifier structurellement le List.

Réparer

Créez un LinkedList, qui prend en charge plus rapidement remove.

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

Sur la splitprise de regex

Depuis l'API:

String.split(String regex): Divise cette chaîne autour des correspondances de l' expression régulière donnée .

|est un métacaractère regex; si vous voulez diviser sur un littéral |, vous devez y échapper \|, ce qui est un littéral de chaîne Java "\\|".

Réparer:

template.split("\\|")

Sur un meilleur algorithme

Au lieu d'appeler removeun à la fois avec des indices aléatoires, il est préférable de générer suffisamment de nombres aléatoires dans la plage, puis de parcourir la Listfois avec un listIterator(), en appelant remove()aux indices appropriés. Il y a des questions sur stackoverflow sur la façon de générer des nombres aléatoires mais distincts dans une plage donnée.

Avec cela, votre algorithme le serait O(N).

polygelubrifiants
la source
Merci, je n'ai que des éléments limités dans la chaîne <10, donc ce ne sera pas un problème d'optimisation.
Pentium10
6
@Pentium: encore une chose: vous ne devriez pas créer une nouvelle instance à Randomchaque fois. Faites-en un staticchamp et semez-le une seule fois.
polygenelubricants
6
LinkedList est-il vraiment plus rapide? LinkedList et ArrayList ont O (n) supprimer ici: \ Il est presque toujours préférable d'utiliser simplement un ArrayList
gengkev
2
LinkedList vs ArrayList -> Il y a un graphique de test de performance de Ryan. LinkedList est plus rapide à supprimer.
torno
LinkedList n'est vraiment plus rapide lors de la suppression que lorsque le nœud à supprimer est déjà connu. Si vous essayez de supprimer un élément, la liste doit être parcourue, chaque élément étant comparé jusqu'à ce que le bon soit trouvé. Si vous essayez de supprimer par index, n traversées doivent être effectuées. Ces traversées sont super chères, et le pire des cas pour la mise en cache CPU: de nombreux sauts autour de la mémoire de manière imprévisible. Voir: youtube.com/watch?v=YQs6IC-vgmo
Alexander - Reinstate Monica
143

Celui-ci m'a brûlé plusieurs fois. Arrays.asListcrée une liste non modifiable. Du Javadoc: Renvoie une liste de taille fixe soutenue par le tableau spécifié.

Créez une nouvelle liste avec le même contenu:

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

Cela créera un peu de déchets supplémentaires, mais vous pourrez les muter.

Nick Orton
la source
6
Petit point, mais vous n'êtes pas "envelopper" la liste d'origine, vous créez une liste complètement nouvelle (c'est pourquoi cela fonctionne).
Jack Leow
Oui, j'ai utilisé Arrays.asList () dans mon cas de test JUnit, qui a ensuite été stocké dans ma carte. Modification de mon code pour copier la liste transmise, dans ma propre liste de tableaux.
cs94njw
Votre solution ne fonctionne pas dans ma situation, mais merci pour l'explication. Les connaissances que vous avez fournies ont conduit à ma solution.
Scott Biggs
54

Probablement parce que vous travaillez avec un wrapper non modifiable .

Modifiez cette ligne:

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

à cette ligne:

List<String> list = new LinkedList<>(Arrays.asList(split));
romain
la source
5
Arrays.asList () n'est pas un wrapper non modifiable.
Dimitris Andreou
@polygenelubricants: il semble que vous vous mélangez unmodifiableet immutable. unmodifiablesignifie exactement "modifiable, mais pas structurellement".
Roman
2
J'ai juste essayé de créer un unmodifiableListwrapper et d'essayer un set; ça jette UnsupportedOperationException. Je suis certain que Collections.unmodifiable*cela signifie vraiment une immuabilité totale, pas seulement structurelle.
polygenelubricants
1
En lisant ces commentaires 7 ans plus tard, je me permets d'indiquer ce lien: stackoverflow.com/questions/8892350/… susceptible de corriger la différence entre immuable et non modifiable, discuté ici.
Nathan Ripert
14

Je pense que remplacer:

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

avec

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

résout le problème.

Salim Hamidi
la source
5

La liste renvoyée par est Arrays.asList()peut-être immuable. Pourriez-vous essayer

List<String> list = new ArrayList(Arrays.asList(split));
Pierre
la source
1
qu'il supprime, ArrayList n'est pas la meilleure structure de données pour supprimer ses valeurs. LinkedList feint beaucoup plus pour son problème.
Roman
2
Mauvais concernant la LinkedList. Il accède par index, donc LinkedList passerait autant de temps à trouver un élément par itération. Voir ma réponse pour une meilleure approche, en utilisant une ArrayList.
Dimitris Andreou
4

Lisez simplement le JavaDoc pour la méthode asList:

Renvoie une {@code List} des objets dans le tableau spécifié. La taille de la {@code List} ne peut pas être modifiée, c'est-à-dire que l'ajout et la suppression ne sont pas pris en charge, mais les éléments peuvent être définis. La définition d'un élément modifie le tableau sous-jacent.

Cela vient de Java 6, mais il semble que ce soit la même chose pour Android Java.

ÉDITER

Le type de la liste résultante est Arrays.ArrayList, qui est une classe privée dans Arrays.class. En pratique, ce n'est rien d'autre qu'une vue de liste sur le tableau avec lequel vous êtes passé Arrays.asList. Avec une conséquence: si vous modifiez le tableau, la liste est également modifiée. Et comme un tableau n'est pas redimensionnable, l'opération de suppression et d'ajout doit être non prise en charge.

Andreas Dolk
la source
4

Arrays.asList () renvoie une liste qui ne permet pas les opérations affectant sa taille (notez que ce n'est pas la même chose que "non modifiable").

Vous pouvez faire new ArrayList<String>(Arrays.asList(split));pour créer une copie réelle, mais en voyant ce que vous essayez de faire, voici une suggestion supplémentaire (vous avez un O(n^2)algorithme juste en dessous).

Vous souhaitez supprimer list.size() - count(appelons cela k) des éléments aléatoires de la liste. Il suffit de sélectionner autant d'éléments aléatoires et de les échanger aux kpositions finales de la liste, puis de supprimer toute la plage (par exemple, en utilisant subList () et clear () à ce sujet). Cela le transformerait en un O(n)algorithme maigre et moyen ( O(k)est plus précis).

Mise à jour : Comme indiqué ci-dessous, cet algorithme n'a de sens que si les éléments ne sont pas ordonnés, par exemple si la liste représente un sac. Si, en revanche, la Liste a un ordre significatif, cet algorithme ne le conserverait pas (l'algorithme des polygénubrifiants le ferait à la place).

Mise à jour 2 : Donc, rétrospectivement, un meilleur algorithme (linéaire, maintenant l'ordre, mais avec des nombres aléatoires O (n)) ressemblerait à ceci:

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
la source
1
+1 pour l'algorithme, bien que OP indique qu'il n'y a que 10 éléments. Et une belle façon d'utiliser les nombres aléatoires avec ArrayList. Beaucoup plus simple que ma suggestion. Je pense que cela entraînerait cependant une réorganisation des éléments.
polygenelubricants
4

J'ai une autre solution à ce problème:

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

travailler sur newList;)

ZZ 5
la source
2

Cette exception UnsupportedOperationException survient lorsque vous essayez d'effectuer une opération sur une collection là où elle n'est pas autorisée et dans votre cas, lorsque vous l'appelez, Arrays.asListelle ne renvoie pas a java.util.ArrayList. Il renvoie un java.util.Arrays$ArrayListqui est une liste immuable. Vous ne pouvez pas y ajouter ni en supprimer.

Mayank Gupta
la source
2

Oui, le Arrays.asList retour d'une liste de taille fixe.

Outre l'utilisation d'une liste chaînée, utilisez simplement addAll liste des méthodes.

Exemple:

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

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

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

parentRecepeIdList.add("555");
Sameer Kazi
la source
2

Remplacer

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

à

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

ou

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

ou

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

ou (mieux pour supprimer des éléments)

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

Arraylist narraylist = Arrays.asList (); // Retourne une liste immuable Pour la rendre mutable, la solution serait: Arraylist narraylist = new ArrayList (Arrays.asList ());

Bruce Wayne
la source
1
Bienvenue chez SO. Bien que nous vous remercions pour votre réponse, il serait préférable qu'elle apporte une valeur supplémentaire en plus des autres réponses. Dans ce cas, votre réponse n'apporte aucune valeur supplémentaire, car un autre utilisateur a déjà publié cette solution. Si une réponse précédente vous a été utile, vous devez la voter une fois que vous avez suffisamment de réputation.
technogeek1995
1

Voici un extrait de code provenant de tableaux

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;

donc ce qui se passe est que lorsque la méthode asList est appelée, elle retourne la liste de sa propre version de classe statique privée qui ne remplace pas la fonction d'ajout de AbstractList pour stocker l'élément dans le tableau. Donc, par défaut, la méthode add dans la liste abstraite lève une exception.

Ce n'est donc pas une liste de tableaux régulière.

Gagandeep Singh
la source
1

Vous ne pouvez pas supprimer ni ajouter à une liste de tableaux à taille fixe.

Mais vous pouvez créer votre sous-liste à partir de cette liste.

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

* L'autre manière est

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

cela créera ArrayList qui n'est pas de taille fixe comme Arrays.asList

Venkat
la source
0

Arrays.asList() utilise un tableau de taille fixe en interne.
Vous ne pouvez pas ajouter ou supprimer de manière dynamiqueArrays.asList()

Utilisez ceci

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

Dans, narraylistvous pouvez facilement ajouter ou supprimer des éléments.

Roushan Kumar
la source
0

Créer une nouvelle liste et remplir des valeurs valides dans une nouvelle liste a fonctionné pour moi.

Erreur de lancement de code -

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

Après correction -

 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
la source