Por que obtenho uma UnsupportedOperationException ao tentar remover um elemento de uma Lista?

476

Eu tenho este código:

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

Eu entendi isso:

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)

Como seria isso da maneira correta? Java.15

Pentium10
fonte
use LinkedList.
Lova Chittumuri 15/03/19

Respostas:

1006

Alguns problemas com seu código:

Ao Arrays.asListretornar uma lista de tamanho fixo

Na API:

Arrays.asList: Retorna uma lista de tamanho fixo apoiada pela matriz especificada.

Você não pode addfazer isso; você não pode removedisso. Você não pode modificar estruturalmente oList .

Consertar

Crie um LinkedList, que suporte mais rápido remove.

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

Ao splittomar regex

Na API:

String.split(String regex): Divide essa sequência em torno das correspondências da expressão regular especificada .

|é um metacaractere regex; se você deseja dividir em um literal |, você deve escapá-lo para \|, o que, como um literal de string Java, é "\\|".

Consertar:

template.split("\\|")

Em um algoritmo melhor

Em vez de chamar removeum de cada vez com índices aleatórios, é melhor gerar números aleatórios suficientes no intervalo e, em seguida, percorrer a Listúnica vez com a listIterator(), chamandoremove() nos índices apropriados. Há perguntas sobre o stackoverflow sobre como gerar números aleatórios, mas distintos, em um determinado intervalo.

Com isso, seu algoritmo seria O(N).

poligenelubricants
fonte
Obrigado, eu tenho apenas elementos limitados na string <10, portanto não será um problema de otimização.
usar o seguinte comando
6
@ Pentium: mais uma coisa: você não deve criar uma nova instância de Randomtoda vez. Faça um staticcampo e semeie apenas uma vez.
polygenelubricants
6
O LinkedList é realmente mais rápido? Ambos LinkedList e ArrayList tem O (n) remover aqui: \ É quase sempre melhor usar apenas um ArrayList
gengkev
2
LinkedList vs ArrayList -> Existe um gráfico de teste de desempenho de Ryan. O LinkedList é mais rápido na remoção.
20/07/15
O LinkedList é muito mais rápido na remoção quando o a ser removido já é conhecido. Se você estiver tentando remover um elemento, a lista deverá ser percorrida, com cada elemento sendo comparado até que o correto seja encontrado. Se você está tentando remover por índice, n passagens devem ser feitas. Essas travessias são super caras e o pior caso para o cache da CPU: muitos saltos pela memória de maneiras imprevisíveis. Veja: youtube.com/watch?v=YQs6IC-vgmo
Alexander - Reinstate Monica
143

Este me queimou muitas vezes. Arrays.asListcria uma lista não modificável. No Javadoc: retorna uma lista de tamanho fixo apoiada pela matriz especificada.

Crie uma nova lista com o mesmo conteúdo:

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

Isso criará um pouco de lixo extra, mas você poderá modificá-lo.

Nick Orton
fonte
6
Ponto secundário, mas você não está "encapsulando" a lista original, está criando uma lista completamente nova (e é por isso que funciona).
amigos estão dizendo sobre jose leow
Sim, usei Arrays.asList () no meu caso de teste JUnit, que foi armazenado dentro do meu mapa. Mudei meu código para copiar a lista passada em minha própria ArrayList.
Cs94njw
Sua solução não funciona na minha situação, mas obrigado pela explicação. O conhecimento que você forneceu levou à minha solução.
Scott Biggs
54

Provavelmente porque você está trabalhando com um invólucro não modificável .

Mude esta linha:

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

para esta linha:

List<String> list = new LinkedList<>(Arrays.asList(split));
romano
fonte
5
Arrays.asList () não é um invólucro não modificável.
Dimitris Andreou
@polygenelubricants: parece que você mistura unmodifiablee immutable. unmodifiablesignifica exatamente "modificável, mas não estruturalmente".
Roman
2
Eu apenas tentei criar um unmodifiableListwrapper e tentar um set; joga UnsupportedOperationException. Estou certo de que Collections.unmodifiable*realmente significa imutabilidade total, não apenas estrutural.
polygenelubricants
1
Lendo esses comentários, sete anos depois, permito-me indicar este link: stackoverflow.com/questions/8892350/… provavelmente corrigirá a diferença entre imutável e não modificável, discutido aqui.
precisa
14

Eu acho que substituindo:

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

com

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

resolve o problema.

Salim Hamidi
fonte
5

A lista retornada por Arrays.asList()pode ser imutável. Você poderia tentar

List<String> list = new ArrayList(Arrays.asList(split));
Pierre
fonte
1
ele está excluindo, ArrayList não é a melhor estrutura de dados para excluir seus valores. O LinkedList tem muito mais a ver com o seu problema.
Roman
2
Errado em relação ao LinkedList. Ele está acessando por índice, portanto o LinkedList gastaria tanto tempo para encontrar um elemento através da iteração. Veja minha resposta para uma melhor abordagem, usando um ArrayList.
Dimitris Andreou
4

Basta ler o JavaDoc para o método asList:

Retorna uma {@code List} dos objetos na matriz especificada. O tamanho da {@code List} não pode ser modificado, ou seja, adicionar e remover não é suportado, mas os elementos podem ser definidos. Definir um elemento modifica a matriz subjacente.

Este é do Java 6, mas parece que é o mesmo para o java android.

EDITAR

O tipo da lista resultante é Arrays.ArrayList, que é uma classe privada dentro de Arrays.class. Na prática, nada mais é do que uma exibição em lista na matriz com a qual você passou Arrays.asList. Com uma conseqüência: se você alterar a matriz, a lista também será alterada. E como uma matriz não é redimensionável, a operação de remoção e adição não deve ser suportada.

Andreas Dolk
fonte
4

Arrays.asList () retorna uma lista que não permite operações que afetam seu tamanho (observe que isso não é o mesmo que "não modificável").

Você pode fazer new ArrayList<String>(Arrays.asList(split));para criar uma cópia real, mas vendo o que você está tentando fazer, aqui está uma sugestão adicional (você tem um O(n^2)algoritmo logo abaixo).

Você deseja remover list.size() - count(vamos chamar isso k) elementos aleatórios da lista. Basta escolher o maior número de elementos aleatórios e trocá-los para as kposições finais da lista e excluir todo o intervalo (por exemplo, usando subList () e clear () nele). Isso o tornaria um O(n)algoritmo enxuto e médio ( O(k)é mais preciso).

Atualização : Conforme observado abaixo, esse algoritmo só faz sentido se os elementos não estiverem ordenados, por exemplo, se a Lista representar um Saco. Se, por outro lado, a Lista tiver uma ordem significativa, esse algoritmo não a preservará (o algoritmo dos pologenelubricantes, em vez disso).

Atualização 2 : Então, retrospectivamente, um algoritmo melhor (linear, mantendo a ordem, mas com O (n) números aleatórios)) seria algo como isto:

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
fonte
1
+1 para o algoritmo, embora o OP diga que existem apenas 10 elementos. E boa maneira de usar os números aleatórios com ArrayList. Muito mais simples do que minha sugestão. Eu acho que resultaria na reordenação dos elementos.
polygenelubricants
4

Eu tenho outra solução para esse problema:

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

trabalhar em newList;)

ZZ 5
fonte
2

Essa UnsupportedOperationException ocorre quando você tenta executar alguma operação na coleção onde não é permitido e, no seu caso, quando você chama Arrays.asList, não retorna a java.util.ArrayList. Retorna uma java.util.Arrays$ArrayListlista imutável. Você não pode adicionar e nem remover.

Mayank Gupta
fonte
2

Sim, Arrays.asListretornando uma lista de tamanho fixo.

Além de usar uma lista vinculada, basta usar a addAlllista de métodos.

Exemplo:

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

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

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

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

Substituir

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

para

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 (Melhor para remover elementos)

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

Arraylist narraylist = Arrays.asList (); // Retorna arraylist imutável Para torná-lo uma solução mutável, seria: Arraylist narraylist = new ArrayList (Arrays.asList ());

Bruce Wayne
fonte
1
Bem-vindo ao SO. Apesar de agradecermos sua resposta, seria melhor se ela fornecesse valor adicional além das outras respostas. Nesse caso, sua resposta não fornece valor adicional, pois outro usuário já postou essa solução. Se uma resposta anterior foi útil para você, você deve votar uma vez que tenha reputação suficiente.
technogeek1995
1

A seguir, trecho de código de matrizes

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;

Então, o que acontece é que, quando o método asList é chamado, ele retorna a lista de sua própria versão de classe estática privada, que não substitui a função add de AbstractList para armazenar o elemento na matriz. Portanto, por padrão, o método add na lista abstrata lança exceção.

Portanto, não é uma lista regular de matrizes.

Gagandeep Singh
fonte
1

Você não pode remover nem adicionar a uma lista de matrizes de tamanho fixo.

Mas você pode criar sua sublist a partir dessa 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, ", ");
}

* Outra maneira é

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

isso criará ArrayList que não é de tamanho fixo como Arrays.asList

Venkat
fonte
0

Arrays.asList() usa matriz de tamanho fixo internamente.
Você não pode adicionar ou remover dinamicamente desteArrays.asList()

Usa isto

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

Em narraylistvocê pode facilmente adicionar ou remover itens.

Roushan Kumar
fonte
0

Criar uma nova lista e preencher valores válidos na nova lista funcionou para mim.

Erro de lançamento de código -

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

Após correção -

 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
fonte