¿Por qué obtengo una UnsupportedOperationException cuando intento eliminar un elemento de una Lista?

476

Tengo este codigo:

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

Entiendo esto:

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)

¿Cómo sería esta la forma correcta? Java.15

Pentium10
fuente
usa LinkedList.
Lova Chittumuri

Respuestas:

1007

Algunos problemas con su código:

Al Arrays.asListdevolver una lista de tamaño fijo

De la API:

Arrays.asList: Devuelve una lista de tamaño fijo respaldada por la matriz especificada.

No puedes addhacerlo; no puedes removede eso. No se puede modificar estructuralmente el List.

Reparar

Cree un LinkedList, que admite más rápido remove.

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

Al splittomar expresiones regulares

De la API:

String.split(String regex): Divide esta cadena alrededor de coincidencias de la expresión regular dada .

|es un metacarácter regex; si desea dividir en un literal |, debe escapar a él \|, que es un literal de cadena Java "\\|".

Reparar:

template.split("\\|")

En mejor algoritmo

En lugar de llamar removeuno a la vez con índices aleatorios, es mejor generar suficientes números aleatorios en el rango, y luego atravesar Listuna vez con un listIterator(), llamando remove()a los índices apropiados. Hay preguntas sobre stackoverflow sobre cómo generar números aleatorios pero distintos en un rango dado.

Con esto, su algoritmo sería O(N).

poligenelubricantes
fuente
Gracias, solo tengo elementos limitados en la cadena <10, por lo que no será un problema de optimización.
Pentium10
66
@Pentium: una cosa más: no deberías crear una nueva instancia Randomcada vez. Hazlo un staticcampo y siembra solo una vez.
polygenelubricants
66
¿LinkedList es realmente más rápido? Tanto LinkedList como ArrayList tienen O (n) eliminar aquí: \ Casi siempre es mejor usar una ArrayList
gengkev
2
LinkedList vs ArrayList -> Hay un gráfico de prueba de rendimiento de Ryan. LinkedList es más rápido en la eliminación.
torno
LinkedList solo es realmente más rápido en la eliminación cuando ya se conoce el nodo que se va a eliminar. Si está tratando de eliminar un elemento, la lista debe atravesarse, comparándose cada elemento hasta que se encuentre el correcto. Si está intentando eliminar por índice, se deben realizar n recorridos. Estos recorridos son súper caros y el peor caso para el almacenamiento en caché de la CPU: muchos saltos alrededor de la memoria de maneras impredecibles. Ver: youtube.com/watch?v=YQs6IC-vgmo
Alexander - Restablece a Mónica el
143

Este me ha quemado muchas veces. Arrays.asListcrea una lista no modificable. Desde el Javadoc: Devuelve una lista de tamaño fijo respaldada por la matriz especificada.

Crea una nueva lista con el mismo contenido:

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

Esto creará un poco de basura extra, pero podrás mutarlo.

Nick Orton
fuente
66
Punto menor, pero no está "ajustando" la lista original, está creando una lista completamente nueva (por eso funciona).
Jack Leow
Sí, utilicé Arrays.asList () en mi caso de prueba JUnit, que luego se almacenó dentro de mi mapa. Cambié mi código para copiar la lista pasada en mi propia ArrayList.
cs94njw
Su solución no funciona en mi situación, pero gracias por la explicación. El conocimiento que proporcionó condujo a mi solución.
Scott Biggs
54

Probablemente porque estás trabajando con un contenedor no modificable .

Cambia esta línea:

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

a esta línea:

List<String> list = new LinkedList<>(Arrays.asList(split));
romano
fuente
55
Arrays.asList () no es un contenedor no modificable.
Dimitris Andreou
@polygenelubricants: parece que te confundes unmodifiabley immutable. unmodifiablesignifica exactamente "modificable, pero no estructuralmente".
Roman
2
Intenté crear un unmodifiableListcontenedor y probar un set; lanza UnsupportedOperationException. Estoy bastante seguro de que Collections.unmodifiable*realmente significa inmutabilidad total, no solo estructural.
polygenelubricants
1
Leyendo esos comentarios 7 años después, me permito indicar este enlace: stackoverflow.com/questions/8892350/... que probablemente solucione la diferencia entre inmutable e inmodificable, discutido aquí.
Nathan Ripert
14

Creo que reemplazando:

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

con

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

resuelve el problema

Salim Hamidi
fuente
5

La lista devuelta por Arrays.asList()podría ser inmutable. Podrías intentar

List<String> list = new ArrayList(Arrays.asList(split));
Pierre
fuente
1
está eliminando, ArrayList no es la mejor estructura de datos para eliminar sus valores. LinkedList tiene muchos más problemas por su problema.
Roman
2
Incorrecto con respecto a la LinkedList. Está accediendo por índice, por lo que LinkedList pasaría tanto tiempo para encontrar un elemento a través de la iteración. Vea mi respuesta para un mejor enfoque, usando una ArrayList.
Dimitris Andreou
4

Simplemente lea el JavaDoc para el método asList:

Devuelve una {@code List} de los objetos en la matriz especificada. El tamaño de la {@code List} no se puede modificar, es decir, agregar y eliminar no son compatibles, pero los elementos se pueden configurar. Establecer un elemento modifica la matriz subyacente.

Esto es de Java 6 pero parece que es lo mismo para el Android Java.

EDITAR

El tipo de la lista resultante es Arrays.ArrayList, que es una clase privada dentro de Arrays.class. En términos prácticos, no es más que una vista de lista en la matriz con la que ha pasado Arrays.asList. Con una consecuencia: si cambia la matriz, la lista también cambia. Y debido a que una matriz no es redimensionable, la operación de eliminar y agregar no debe ser compatible.

Andreas Dolk
fuente
4

Arrays.asList () devuelve una lista que no permite operaciones que afecten su tamaño (tenga en cuenta que esto no es lo mismo que "no modificable").

Podría hacer new ArrayList<String>(Arrays.asList(split));para crear una copia real, pero viendo lo que está tratando de hacer, aquí hay una sugerencia adicional (tiene un O(n^2)algoritmo justo debajo de eso).

Desea eliminar list.size() - count(llamemos a esto k) elementos aleatorios de la lista. Simplemente elija tantos elementos aleatorios y cámbielos a las kposiciones finales de la lista, luego elimine todo ese rango (por ejemplo, usando subList () y clear () en eso). Eso lo convertiría en un O(n)algoritmo magro y medio ( O(k)es más preciso).

Actualización : como se indica a continuación, este algoritmo solo tiene sentido si los elementos no están ordenados, por ejemplo, si la Lista representa una Bolsa. Si, por otro lado, la Lista tiene un orden significativo, este algoritmo no lo preservaría (el algoritmo de los poligeneles lubricantes lo haría).

Actualización 2 : Entonces, en retrospectiva, un algoritmo mejor (lineal, manteniendo el orden, pero con números aleatorios O (n)) sería algo como esto:

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
fuente
1
+1 para el algoritmo, aunque OP dice que solo hay 10 elementos. Y buena manera de usar los números aleatorios con ArrayList. Mucho más simple que mi sugerencia. Sin embargo, creo que resultaría en un reordenamiento de los elementos.
polygenelubricants
4

Tengo otra solución para ese problema:

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

trabajar en newList;)

ZZ 5
fuente
2

Esta excepción de operación no admitida se produce cuando intenta realizar alguna operación en la recopilación donde no está permitido y, en su caso, cuando llama Arrays.asListno devuelve a java.util.ArrayList. Devuelve un java.util.Arrays$ArrayListque es una lista inmutable. No puede agregarle ni eliminarlo.

Mayank Gupta
fuente
2

Sí, en Arrays.asList, devolviendo una lista de tamaño fijo.

Aparte de usar una lista vinculada, simplemente use la addAlllista de métodos.

Ejemplo:

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

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

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

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

Reemplazar

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

a

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

o

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

o

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

o (Mejor para eliminar elementos)

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

Arraylist narraylist = Arrays.asList (); // Devuelve una lista de matrices inmutable Para que sea una solución mutable sería: Arraylist narraylist = new ArrayList (Arrays.asList ());

Bruce Wayne
fuente
1
Bienvenido a SO. Aunque le agradecemos su respuesta, sería mejor si proporcionara un valor adicional además de las otras respuestas. En este caso, su respuesta no proporciona un valor adicional, ya que otro usuario ya publicó esa solución. Si una respuesta anterior fue útil para usted, debe votarla una vez que tenga suficiente reputación.
technogeek1995
1

A continuación se muestra un fragmento de código de matrices

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;

Entonces, lo que sucede es que cuando se llama al método asList, devuelve una lista de su propia versión de clase estática privada que no anula la función agregar de AbstractList para almacenar el elemento en la matriz. Entonces, por defecto, el método add en la lista abstracta arroja una excepción.

Por lo tanto, no es una lista de matriz regular.

Gagandeep Singh
fuente
1

No puede eliminar ni agregar a una lista de matrices de tamaño fijo.

Pero puedes crear tu sublista desde esa lista.

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

* Otra forma es

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

esto creará ArrayList que no tiene un tamaño fijo como Arrays.asList

Venkat
fuente
0

Arrays.asList() utiliza una matriz de tamaño fijo internamente.
No puede agregar o quitar dinámicamente de estoArrays.asList()

Utilizar este

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

En narraylistpuedes agregar o quitar elementos fácilmente.

Roushan Kumar
fuente
0

Crear una nueva lista y completar valores válidos en una nueva lista me funcionó.

Error de lanzamiento de código:

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

Después de arreglarlo

 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
fuente