[RESOLVIDO] Exceção java.util.ConcurrentModificationException, como resolver?

Olá pessoal,

Fiz várias pesquisas encontrei muita gente falando que resolveu o problema, mas cada um de uma forma diferente.
Eu só preciso de uma explicação de como resolver esse problema. Não preciso de código pronto, mas um exemplo pode ajudar.

Estou tentando fazer um programa que calcule o espaço amostral de uma combinação simples com 6 elementos tomados de 2 a 2.
É sabido que o espaço amostral dessa combinação possui 15 elementos de 2 números.

Exemplo:

1 - tenho os números de 1 a 6;
2 - quero lançar eventos de 2 números (ou seja, sortear de 2 números);

Desenvolvi o seguinte código, mas na execução sempre é ocorre a exceção java.util.ConcurrentModificationException.
Eu já vi inclusive o tópico: http://www.guj.com.br/java/30323-erro-com-collections-javautilconcurrentmodificationexception,
aqui do fórum, mas não entendi as soluções propostas e preciso de algumas explicações. Alguém pode me ensinar a pescar?

O código que desenvolvi é o seguinte:
(mais abaixo tem o log)

	public void calcularEspacoAmostral() {

		/*
		 * Lista, numeros para armazenar os elementos usados para tirar os eventos.
		 * Lista espacoAmostral que contém todos os elementos.
		 * Lista sublist que contém um evento.
		 */
		List<Integer> numeros = new ArrayList<Integer>();
		List<List> espacoAmostral = new ArrayList<List>();
		List<Integer> sublist = new ArrayList<Integer>();
		
		//Criando os números
		int n1 = 1;

		while (n1 < 7) {
			numeros.add(n1);
			n1++;
		}

		long n2 = 0;
		while (n2 < 16) {
			Collections.shuffle(numeros); //Embaralha
			sublist = numeros.subList(0, 2); //gera um novo elemento
			Collections.sort(sublist); //ordena os elementos no novo elemento
			
			if (n2 == 0) { //Adiciona o primeiro elemento ao espaço amostral.
				
				espacoAmostral.add(sublist);
				
			} else {
			
			//A partir do primeiro elemento, o espaço amostral deve ser percorrido para comparar se a nova sublist já contém.
				for (List l : espacoAmostral) {

					if (l.contains(sublist)) {
						System.out.println("Já contém.");
					} else {
						espacoAmostral.add(sublist);
					}
					
				}
				
			}

			System.out.println("Lista de sorteios: " + espacoAmostral.toString());
			n2++;
		}

	}

Log:

0 - Lista de sorteios: [[3, 6]]
1 - Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
	at java.util.ArrayList$Itr.next(ArrayList.java:831)
	at pacote.ProbabilidadeEEstatisticaController.calcularEspacoAmostral(ProbabilidadeEEstatisticaController.java:153)
	at pacote.SorteioTeste.main(Teste.java:19)

Altera para:

[code]
public static void calcularEspacoAmostral() {

    /* 
     * Lista, numeros para armazenar os elementos usados para tirar os eventos. 
     * Lista espacoAmostral que contém todos os elementos. 
     * Lista sublist que contém um evento. 
     */  
    List<Integer> numeros = new ArrayList<Integer>();  
    List<List<Integer>> espacoAmostral = new ArrayList<List<Integer>>();  
    List<Integer> sublist;  
      
    //Criando os números  
    int n1 = 1;  
  
    while (n1 < 7) {  
        numeros.add(n1);  
        n1++;  
    }  
  
    long n2 = 0;  
    while (n2 < 15) {  
    	
        Collections.shuffle(numeros); //Embaralha  
        sublist = new ArrayList<Integer>(numeros.subList(0, 2)); //gera um novo elemento  
        Collections.sort(sublist); //ordena os elementos no novo elemento  
          
   
        if (espacoAmostral.contains(sublist)) {
            System.out.println("Já contém." + sublist.toString());
        } else {
            espacoAmostral.add(sublist);
            n2++;
        } 
  
        System.out.println("Lista de sorteios: " + espacoAmostral.toString());  
          
    }  
  
}  [/code]

Estava esquecendo do “ensinar a pescar”, esse erro ocorre quando uma lista está sendo “varrida” (iterada) e ocorre uma modificação na mesma (no caso um add), a implementação detecta essa alteração e lança o erro.

Outro detalhe: subList retorna uma mesma referencia para numeros, então é fundamental criar uma nova lista.

Sua solução tem um problema para espaços com muitos elementos, uma sugestão é gerar o espaço em ordem e fazer o embaralhamento no espaço.

[quote=A H Gusukuma]Estava esquecendo do “ensinar a pescar”, esse erro ocorre quando uma lista está sendo “varrida” (iterada) e ocorre uma modificação na mesma (no caso um add), a implementação detecta essa alteração e lança o erro.

Outro detalhe: subList retorna uma mesma referencia para numeros, então é fundamental criar uma nova lista.

Sua solução tem um problema para espaços com muitos elementos, uma sugestão é gerar o espaço em ordem e fazer o embaralhamento no espaço.
[/quote]

Muito obrigado pela resposta! Agora eu entendi :slight_smile:

Sobre a dica. Eu preciso do espaço ordenado. Com esse algoritmo ele está desordenado pois só foram ordenados as sublistas.
Já vi que o sort() não funciona depois do List<List>.

Se precisa ordenado, poderia gerar em ordem:

List<List<Integer>> espacoAmostral = new ArrayList<List<Integer>>(); for (int i = 1; i < 7 -1; i++) { for (int j = i + 1; j< 7; j++) { sublist = new ArrayList<Integer>(); sublist.add(i); sublist.add(j); espacoAmostral.add(sublist); } } System.out.println("Lista de sorteios: " + espacoAmostral.toString());

Essa última solução é válida, mas no meu caso meu algoritmo vai ser dinâmico, ou seja, quero poder informar a quantidade de elementos que quero sortear de uma determinada quantidade de elementos.

Essa parte de o algoritmo ser dinâmico eu já consegui desenvolver. Inclusive a questão do sort eu já conseguir resolver.
Mas estou com um último problema.

Vou postar um exemplo de como eu mapeei o algoritmo.

Classe de modelo (exemplo):


@Entity
@Table
//@NamedQuery(name="find.all", query="SELECT s FROM Espacoamostral s")
public class EspacoAmostral implements Serializable, Comparable<EspacoAmostral>{
	
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private long id;
	
	private int b_0,b_1;
	
	public EspacoAmostral() {}
	
	@Override
	public int compareTo(EspacoAmostral espacoAmostral) {
		if (this.b_1 < espacoAmostral.b_1) {
			return -1;
		} else {
			return 1;
		}
	}

	public long getId() {
		return id;
	}

	public void setId(long id) {
		this.id = id;
	}

	public int getB_0() {
		return b_0;
	}

	public void setB_0(int b_0) {
		this.b_0 = b_0;
	}

	public int getB_1() {
		return b_1;
	}

	public void setB_1(int b_1) {
		this.b_1 = b_1;
	}

}

Classe de negócio (implementação do método):

	public void calcularEspacoAmostral() throws ClassNotFoundException,
			IllegalAccessException, IllegalArgumentException,
			InvocationTargetException, InstantiationException {

		int contador = 0;
		
		EspacoAmostral ea = new EspacoAmostral();
		
		/*
		 * Lista, numeros para armazenar os elementos usados para tirar os
		 * eventos. Lista espacoAmostral que contém todos os elementos. Lista
		 * sublist que contém um evento.
		 */
		List<EspacoAmostral> objetosAmostral = new ArrayList<EspacoAmostral>();
		List<Integer> numeros = new ArrayList<Integer>();
		List<Integer> sublistAtual = new ArrayList<Integer>();
		List<Integer> sublistAntiga = new ArrayList<Integer>();

                //Utilizando o pacote reflect
		Class c = Class.forName("com.ararazul.model.entidades.EspacoAmostral");
		EspacoAmostral espacoAmostral = (EspacoAmostral) c.newInstance();
		Method methods[] = c.getMethods();

		// Criando os números
		int n1 = 1;

		while (n1 < 7) {
			numeros.add(n1);
			n1++;
		}

		long n2 = 0;
		while (n2 < 15) {
			Collections.shuffle(numeros); //Embaralhando
			sublistAtual = new ArrayList<Integer>(numeros.subList(0, 2)); //Sorteando
			Collections.sort(sublistAtual); //Ordenando o sorteio

                        //Verificando se já existe, se não existir verifica e invoca o método adequado em tempo de execução
                        //passando cada valor da sublistAtual para cada método invocado.
			if (sublistAtual.equals(sublistAntiga)) {

				System.out.println("Já existe: " + sublistAtual);
				// continue;

			} else {

				for (int i = 0; i < sublistAtual.size(); i++) {

					System.out.println("i vale: " + i + " valor da posição: " + sublistAtual.get(i));
					
					for (Method m : c.getMethods()) {

						String[] split = m.getName().split("_");

						if (split.length > 1 && split[0].equals("setB")) {

							String s1 = split[0];
							int s2 = Integer.parseInt(split[1]);

							// invocação do método em tempo de execução.
							if (s2 == contador) {
								m.invoke(espacoAmostral, sublistAtual.get(contador));
								contador++;
							}
						}
						
					}
					
					contador = 0;
				}

				n2++;

			}
			objetosAmostral.add(espacoAmostral);
			sublistAntiga = sublistAtual;
			sublistAtual = null;
			System.out.println(objetosAmostral.size());
			Collections.sort(objetosAmostral);
			
		}
                
                //O problema está aqui.
                //O resultado impresso é sempre a última sublistAtual 15 vezes.
		for (EspacoAmostral e : objetosAmostral) {
			System.out.println(e.getB_0() + ", " + e.getB_1());
		}

	}

Defina a lista objetosAmostral fora do escopo do método, como está, sempre vai ser criado uma nova lista em cada invocação do método.

Vários problemas no teu código:

  • Dizes que queres ter uma solução dinâmica mas tens uma class com duas variaveis b_0 e b_1. Isto não é dinâmico.
  • Usas reflection para invocar uma instância da própria classe onde estás. Não vejo a necessidade.
  • No sorteio apenas estás a comparar com a última sublist. Se for igual a uma que já saiu mas não a última, vai adicionar na mesma
  • Se queres todos os casos possíveis não deves sequer fazer qualquer sorteio mas sim varrer todas as soluções.

O que tu precisas é de algo recursivo. Feito em cima do joelho, mas será mais ou menos assim:

[code]
public class EspacoAmostral {
List<List> espacoAmostral = new ArrayList<List>();

public static void main(String[] args) {
    List<Integer> numeros = Arrays.asList(1, 2, 3, 4, 5, 6);  // lista de números 
    int n = 2; // número de elementos a sortear

    new EspacoAmostral().calcularEspacoAmostral(numeros, n);


}

public void calcularEspacoAmostral(List<Integer> numeros, int n) {

    recursiveIteration(new ArrayList<Integer>(), numeros, n);  //chama método recursivo dado que n é dinamico

    for (List<Integer> e : espacoAmostral) {
        System.out.print("=> ");
        for (Integer i : e) {
            System.out.print(i + " ");
        }
        System.out.println();
    }

}

void recursiveIteration(List<Integer> currentList, List<Integer> toCheck, int n) {
    if (currentList.size() == n) {     
        espacoAmostral.add(currentList);
        return;
    }

// aqui está a lógica necessária: varre os números da lista que ainda não verificou e cria uma cópia para cada um deles
List newToCheck = new ArrayList(toCheck);
for (Integer x : toCheck) {
List newList = new ArrayList(currentList);
newList.add(x);
newToCheck.remove(x);

        recursiveIteration(newList, newToCheck, n);  // chama o próprio método até n ser igual ao tamanho da lista 
    }


}

}[/code]

[quote=pmlm]Vários problemas no teu código:

  • Dizes que queres ter uma solução dinâmica mas tens uma class com duas variaveis b_0 e b_1. Isto não é dinâmico.

  • Usas reflection para invocar uma instância da própria classe onde estás. Não vejo a necessidade.

  • No sorteio apenas estás a comparar com a última sublist. Se for igual a uma que já saiu mas não a última, vai adicionar na mesma

  • Se queres todos os casos possíveis não deves sequer fazer qualquer sorteio mas sim varrer todas as soluções.
    [/quote]

  • Dizes que queres ter uma solução dinâmica mas tens uma class com duas variaveis b_0 e b_1. Isto não é dinâmico.
    Eu postei apenas um exemplo, claro que o código que eu postei não está dinâmico. Mas valeu pelas dicas.

  • Usas reflection para invocar uma instância da própria classe onde estás. Não vejo a necessidade.
    Respondendo à sua pergunta: EspacoAmostral espacoAmostral = (EspacoAmostral) c.newInstance();
    Há a necessidade sim, pois o meu problema exige que eu chame o método em tempo de execução, se cc souber de outra maneira mais simples do que reflection sou todo ouvidos.

  • No sorteio apenas estás a comparar com a última sublist. Se for igual a uma que já saiu mas não a última, vai adicionar na mesma
    Errado. Após adicionar a o sorteio na lista de sorteios eu a armazeno num sorteio antigo para comparar com o novo sorteio e fazer a comparação se for igual eu gero uma nova, isso evita de eu usar o contains() da lista e eu ganho com performance se eu usar uma lista de milhões de sorteios.

  • Se queres todos os casos possíveis não deves sequer fazer qualquer sorteio mas sim varrer todas as soluções.
    Eu estou tratando de arranjos simples (gerar combinação sem repetição incluindo a ordem dos elementos).

Vou fazer os testes e ao chegar em casa eu edito esta mensagem com os resultados.

Precisas disso para o “dinamismo” que nunca terás assim. Agora tens b_1 e b_2. Dinâmico para ti será ter até b quanto? b_10, b_20?, b_100?
E eu sei bem o que é reflection, fui eu quem te “ensinou” há umas semanas atrás. :slight_smile:

Quando resolveres o teu problema do print vais-me dar razão

[quote=guj1]

  • Se queres todos os casos possíveis não deves sequer fazer qualquer sorteio mas sim varrer todas as soluções.
    Eu estou tratando de arranjos simples (gerar combinação sem repetição incluindo a ordem dos elementos).[/quote]
    Eu sei o que estás a fazer. Estou a dizer que não é a maneira correta de o fazer. Tu não queres fazer nenhum sorteio, queres ter TODAS as combinações possíveis. Isso faz-se varrendo a lista de números e gerando combinações, não sorteando e verificando se já existem. Ainda para mais, estás a partir do pressuposto que sabes o número de combinações existentes (o teu 15). Como será quando tiveres de fazer combinações de 100 números, 7 a 7? Vais calcular à mão primeiro o número de combinações?

Nota que o objetivo destes comentários não é “dizer mal” da tua solução mas apenas por-te a pensar pelo caminho correto a seguir. Acredita que a maneira que estás a fazer não é a correta e se fores avante com ela o único prejudicado serás tu e mais ninguém.

Bom, vamos lá, pmim.

Bom vou explicar exatamente o que estou fazendo ok?

Claro que você está falando por mal, eu entendo, eu sei ouvir uma boa crítica e discutir sem nenhum problema. Por isso, estou gostando muito das suas dicas e as do A H Gusukuma.

Explicando o que estou tentando fazer.

Eu vou preciso gerar 6 espaços amostrais, cada um deles possui uma natureza diferente. E eu sei exatamente a quantidade de elementos que cada um contém. Na casa de milhões.

Em suma o que eu preciso é gerar os arranjos simples e persistir num database.

Para isso eu pensei, vou gerar cara combinação com um Collections.shuffle ordeno e adiciono numa lista se ela não existir. Ao final eu ordeno a lista de arranjos. Para persistir num database eu crio uma entidade que contenha uma lista dos objetos (espaço amostral).
Por isso, eu teria que atribuir o valor de cada elemento de cada arranjo a cada atributo da entidade, daí o motivo de eu precisar chamar os métodos da entidade em tempo de execução para verificar o método correto pois ela possui outros como o id, toString e outros. Entendeu? Não sei se me expressei bem.

Ah! Eu não entendi muito bem a sua dica. (a que contém o código fonte)

[quote=A H Gusukuma]Se precisa ordenado, poderia gerar em ordem:

List<List<Integer>> espacoAmostral = new ArrayList<List<Integer>>(); for (int i = 1; i < 7 -1; i++) { for (int j = i + 1; j< 7; j++) { sublist = new ArrayList<Integer>(); sublist.add(i); sublist.add(j); espacoAmostral.add(sublist); } } System.out.println("Lista de sorteios: " + espacoAmostral.toString());

[/quote]

Galera, não deu certo.

Uma pergunta, se os números fossem de 0 a 99 e eu quisesse sortear 10 números por vez, como a solução acima poderia ajudar?
Ficaria muito procedural não?
O que seria preciso modificar nela?

Se tu queres saber todas as combinações, não podes confiar na sorte e ir fazedo shuffle até ter todas. Em primeiro lugar porque não deves partir do pressuposto que sabes quanto são “todas”, logo não sabes quando parar. Depois, porque fazendo o shuflle, no limite podes nunca ter todas já que o sistema pode executar dias e nunca gerar uma delas.

[quote=guj1]
Ah! Eu não entendi muito bem a sua dica. (a que contém o código fonte)[/quote]
A palavra a estudar é Recursividade :slight_smile:

[quote=guj1]
Ah! Eu não entendi muito bem a sua dica. (a que contém o código fonte)[/quote]
A palavra a estudar é Recursividade :)[/quote]

Estudei e entendi a recursividade.
Mudei minha estratégia. Agora só vou usar o algoritmo uma vez. Mesmo usando recursividade não vai ficar performático.
Agora vou gerar apenas 4 espaços amostrais. Portanto, vou usar algo estático mesmo.

[quote=A H Gusukuma]Se precisa ordenado, poderia gerar em ordem:

List<List<Integer>> espacoAmostral = new ArrayList<List<Integer>>(); for (int i = 1; i < 7 -1; i++) { for (int j = i + 1; j< 7; j++) { sublist = new ArrayList<Integer>(); sublist.add(i); sublist.add(j); espacoAmostral.add(sublist); } } System.out.println("Lista de sorteios: " + espacoAmostral.toString());

[/quote]

Sobre essa dica, no caso o número de elementos do espaço amostral é o número de for e todos os for internos devem inicializar um valor somando 1, certo?