Iterador Listas Problema

3 views
Skip to first unread message

MP

unread,
Sep 30, 2008, 8:19:11 AM9/30/08
to ptjug-geral
Oi,

estou com um problema. Tenho um conjunto de objectos numa lista. Estou
a iterá-la. Durante o processo do iterador eu tenho que adicionar mais
elementos à lista. Dá-me uma mensagem excepção
"java.util.ConcurrentModificationException".

Qual é a solução para este problema i.e., como é que posso percorrer
uma lista, adicionando ou removendo se quiser novos elementos à lista
sem dar erro?

Obrigado

MP

rbadaro

unread,
Sep 30, 2008, 8:38:24 AM9/30/08
to ptjug-geral
Boas,

- Podes usar um CopyOnWriteArrayList (java 1.5+) que te permite
iterares e alterares a lista que está por baixo. Como este array cria
uma nova cópia cada vez que o alteras (remover ou adicionar elemento)
tens de ver se te compensa em termos de performance. Além disso é de
notar que o teu iterador vai iterar só pelos elementos que tens no
momento em que é criado, os que adicionas removes ficam apenas na tua
lista.
- Podes fazer sem um iterador, acedendo pelo índice.
- Se fores só remover, podes remover o elemento corrente no iterador
(nomeIterador.remove())

Devem haver mais formas mas estas foram as primeiras que pensei.

Ruben

António Mota

unread,
Sep 30, 2008, 8:55:56 AM9/30/08
to ptjug...@googlegroups.com
Hmmm, eu nao percebo muito de sincronizacao, mas nao deveria bastar fazer isso num bloco sincronizado? Assim mais ninguem altera a lista enquanto se esta a iterar e nao haConcurrentModificationException.

Perdoem-me se estiver a dizer uma asneira.

2008/9/30 rbadaro <ruben....@gmail.com>



--
Melhores cumprimentos / Beir beannacht / Best regards

António Manuel dos Santos Mota

mobile PT: +351919623568 (deprecated)
mobile IE: +353(0)877718363
mail: ams...@gmail.com
skype: amsmota
msn: antoni...@hotmail.com
linkedin: www.linkedin.com/in/amsmota

Rui Campos

unread,
Sep 30, 2008, 9:07:39 AM9/30/08
to ptjug...@googlegroups.com
Olá,

Normalmente quando se está a iterar uma lista, a lista fica imutável para manter a integridade do iterador. Uma solução é usar uma lista auxiliar onde vais colocando os objectos a adicionar e no final de iterares a lista, adicionares todos os elementos da lista auxiliar à lista principal, usando o addAll().

Cumps,
Rui

2008/9/30 MP <rsm...@gmail.com>

Sérgio Ferreira

unread,
Sep 30, 2008, 9:15:02 AM9/30/08
to ptjug...@googlegroups.com
Não é por causa de duas thread(s) estarem a tentar aceder à lista em
simultâneo.
A implementação detecta que a lista que deu origem ao iterador foi
modificada após a sua criação (do iterador).
Adicionar um synchronize não altera absolutamente nada.
Eu acho que deviam era fazer throw de outra excepção diferente. Esta já
me enganou mais do que uma vez.

António Mota escreveu:
> Hmmm, eu nao percebo muito de sincronizacao, mas nao deveria bastar
> fazer isso num bloco sincronizado? Assim mais ninguem altera a lista
> enquanto se esta a iterar e nao haConcurrentModificationException.
>
> Perdoem-me se estiver a dizer uma asneira.
>
> 2008/9/30 rbadaro <ruben....@gmail.com <mailto:ruben....@gmail.com>>
>
>
> Boas,
>
> - Podes usar um CopyOnWriteArrayList (java 1.5+) que te permite
> iterares e alterares a lista que está por baixo. Como este array cria
> uma nova cópia cada vez que o alteras (remover ou adicionar elemento)
> tens de ver se te compensa em termos de performance. Além disso é de
> notar que o teu iterador vai iterar só pelos elementos que tens no
> momento em que é criado, os que adicionas removes ficam apenas na tua
> lista.
> - Podes fazer sem um iterador, acedendo pelo índice.
> - Se fores só remover, podes remover o elemento corrente no iterador
> (nomeIterador.remove())
>
> Devem haver mais formas mas estas foram as primeiras que pensei.
>
> Ruben
>
> On 30 Set, 13:19, MP <rsma...@gmail.com
> <mailto:rsma...@gmail.com>> wrote:
> > Oi,
> >
> > estou com um problema. Tenho um conjunto de objectos numa lista.
> Estou
> > a iterá-la. Durante o processo do iterador eu tenho que
> adicionar mais
> > elementos à lista. Dá-me uma mensagem excepção
> > "java.util.ConcurrentModificationException".
> >
> > Qual é a solução para este problema i.e., como é que posso percorrer
> > uma lista, adicionando ou removendo se quiser novos elementos à
> lista
> > sem dar erro?
> >
> > Obrigado
> >
> > MP
>
>
>
>
> --
> Melhores cumprimentos / Beir beannacht / Best regards
>
> António Manuel dos Santos Mota
>
> mobile PT: +351919623568 (deprecated)
> mobile IE: +353(0)877718363
> mail: ams...@gmail.com <mailto:ams...@gmail.com>
> skype: amsmota
> msn: antoni...@hotmail.com <mailto:antoni...@hotmail.com>
> linkedin: www.linkedin.com/in/amsmota <http://www.linkedin.com/in/amsmota>
>
> >

Luís Tiago Rico

unread,
Sep 30, 2008, 9:45:14 AM9/30/08
to ptjug...@googlegroups.com
Existem sempre outras alternativas como já li por aí, esta é só mais uma.

À tempos tive um problema semelhante, o qual, resolvi facilmente, lendo o
Javadoc!

Ao instaciares a tua lista deves fazer por exemplo:

List x = Collections.synchronizedList(new ArrayList());

Isto funciona com estruturas do tipo Collection...
Collections.synchronizedMap(...
Collections.synchronizedSet(...
Etc...

Espero que ajude.

[]'s
Rico

Ruben Badaró

unread,
Sep 30, 2008, 10:40:56 AM9/30/08
to ptjug...@googlegroups.com
Boas,

Atenção que usar synchronized lists é muito lento porque todas a operações são sincronizadas. E nem se fala se a colecção for realmente partilhada entre threads, aí ainda se nota mais.
Em testes que fiz, para percorrer e remover elementos de listas, é 5X mais lento que uma lista não sincronizada.

A opção do Rui Campos parece-me interessante para quando temos apenas uma thread e deve ser performante. Calculo que no caso das remoções ele as fala através do Iterador.

Cumps,

Ruben Badaró

LinkedIn: http://www.linkedin.com/in/rbadaro
Blog: http://www.zonaj.org



2008/9/30 Luís Tiago Rico <tiag...@gmail.com>

António Mota

unread,
Sep 30, 2008, 10:47:12 AM9/30/08
to ptjug...@googlegroups.com
Ha aqui uma comparacao de performance entre as varias lists, talvez interesse. Basicamente depende das operacoes a efectuar.

http://www.onjava.com/pub/a/onjava/2001/05/30/optimization.html

2008/9/30 Ruben Badaró <ruben....@gmail.com>

--
Melhores cumprimentos / Beir beannacht / Best regards

António Manuel dos Santos Mota

mobile PT: +351919623568 (deprecated)
mobile IE: +353(0)877718363
mail: ams...@gmail.com

António Mota

unread,
Sep 30, 2008, 10:53:22 AM9/30/08
to ptjug...@googlegroups.com
E nao devia o ListIterator fazer isso sem problema?

ListIterator listIterator = aList.listIterator();
listIterator.next();
//remove element returned by last next method
listIterator.remove();




2008/9/30 António Mota <ams...@gmail.com>

Miguel Duarte

unread,
Sep 30, 2008, 12:52:57 PM9/30/08
to ptjug...@googlegroups.com
Não uses iteradores.

A interface List implementa o método http://java.sun.com/j2se/1.5.0/docs/api/java/util/List.html#get(int) que permite aceder ao elemento por índice

for(int i = 0; i != lista.size(); ++i) {
     xpto = l.get(i);
}
 
Apesar de funcionar, esta abordagem é extremamente ineficiente se estiveres a usar uma LinkedList. Se for um ArrayList até deve ser marginalmente mais rápido.


2008/9/30 MP <rsm...@gmail.com>

Manuel Leiria

unread,
Oct 1, 2008, 12:12:53 PM10/1/08
to ptjug...@googlegroups.com
O ListIterator (http://java.sun.com/j2se/1.4.2/docs/api/java/util/ListIterator.html) aplica-se bem ao teu caso.

Miguel Duarte

unread,
Oct 1, 2008, 4:57:55 PM10/1/08
to ptjug...@googlegroups.com
Não, não se aplica. Não se pode modificar o conteúdo da lista sem recorrer ao próprio iterador!

Em Groovy para ficar mais compacto

l = [1,2,3,4,5,6,7,8]
it = l.listIterator()
while(it.hasNext()) {
     println it.next()
     l.add(9)
}

1
Exception thrown: java.util.ConcurrentModificationException

java.util.ConcurrentModificationException
at Script1.run(Script1:6)

É possível adicionar elementos à lista recorrendo ao iterador, mas com uma particularidade, as adições (e já agora as remoções) são sempre feitas na posição anterior à do iterador, pelo que o iterador nunca vai ver o elemento adicionado se continuar a interar para a frente, o que provavelmente também não vai ajudar na questão original - quem está a tentar fazer isto em regra está a tentar adicionar elementos à lista para os "apanhar" mais tarde

l = [1,2,3,4,5,6,7,8]

it = l.listIterator()
it.add(0)
while(it.hasNext()) {
    println it.next()
}
Output:
1
2
3
4
5
6
7
8

Exemplo para as remoções:

l = [1,2,3,4,5,6,7,8]
it = l.listIterator()
while(it.hasNext()) {
    println it.next()
    it.remove()
}

println "Tamanho no final = " + l.size()
Output:
1
2
3
4
5
6
7
8
Tamanho no final = 0

Imprime os elementos todos, mas como apaga o anterior, a lista no final fica vazia.


Já agora, a notação 
for(elemento : lista) 
é apenas "syntax sugar" para iterar pela lista usando iteradores (só que não os vemos), pelo que não se pode alterar a lista pela mesma razão

Se quiserem como está feito é fácil as sources do JDK que acompanham qualquer instalação.

De resto, sim, também acho este comportamento uma estupidez. Limita a funcionalidade, paga-se um custo de performance por verificar e manter o contador de alterações, e ainda por cima, não serve para o que eventualmente poderia ter interesse, detectar modificações concorrentes em diferentes threads. A lista até pode ter sido alterada e a alteração ao contador não ficar visivel se a thread estiver num cpu diferente. Portanto uma inutilidade pegada, as boas intenções não desculpam um design estúpido.

O lado bom da coisa é que existem várias implementações da interface List sem este comportamento asinino, pelo que no código, se só usares as interfaces, é uma solução "drop-in"

Por exemplo aqui http://javolution.org/

Têm uma apresentação interessante aqui:



MD




 



2008/10/1 Manuel Leiria <manuel...@gmail.com>

PJG

unread,
Oct 2, 2008, 6:36:57 AM10/2/08
to ptjug...@googlegroups.com
Boas,


Creio que a proibição de alterar a lista excepto através do iterador quando se está a iterar faz todo o sentido porque a perspectiva é a de garantir que:
1- Todos os elementos originais são iterados;
2- O mesmo elemento não é iterado mais que uma vez;
3- Os novos elementos NÃO são iterados, pela razão que podem encontrar mais abaixo nas minhas respostas.

Restante resposta "inline":

On 2008-10-01, at 21:57, Miguel Duarte wrote:

Não, não se aplica. Não se pode modificar o conteúdo da lista sem recorrer ao próprio iterador!

Em Groovy para ficar mais compacto

l = [1,2,3,4,5,6,7,8]
it = l.listIterator()
while(it.hasNext()) {
     println it.next()
     l.add(9)
}

1
Exception thrown: java.util.ConcurrentModificationException

java.util.ConcurrentModificationException
at Script1.run(Script1:6)

Convenhamos que o código em Java tb. não é assim tão maior:

List<Integer>     l  = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
Iterator<Integer> it = l.iterator();
while (it.hasNext()) {
System.out.println(it.next());
l.add(9);
}

...que aliás é a mesma coisa que isto:

List<Integer> l = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
for (Integer it : l) {
System.out.println(it);
l.add(9);
}



É possível adicionar elementos à lista recorrendo ao iterador, mas com uma particularidade, as adições (e já agora as remoções) são sempre feitas na posição anterior à do iterador, pelo que o iterador nunca vai ver o elemento adicionado se continuar a interar para a frente, o que provavelmente também não vai ajudar na questão original - quem está a tentar fazer isto em regra está a tentar adicionar elementos à lista para os "apanhar" mais tarde

Obviamente faz todo o sentido que o iterador remova o último elemento retornado, que foi aquele que tiveste a oportunidade de analisar.

A decisão de colocar as adições na posição anterior à do iterador é CORRECTA e prende-se com a preocupação de proporcionar a maior flexibilidade possível.
 
Se fosse ao contrário eras forçado a apanhar os novos registos quer os quisesses processar da mesma forma que os elementos originais da lista ou não.

Assim, não és forçado a processar os novos elementos se não quiseres mas se quiseres tens sempre a solução de utilizar (por exemplo) uma rotina recursiva se (por exemplo) quiseres extrair outros elementos daqueles que estás a processa.
Estás errado. A implementação que estás a ver NÃO É thread safe.

Estupidez seria fazer-te pagar o custo de utilizar sincronização numa implementação que te garantem ser livre desse custo. Se queres utilizar uma lista sincronizada, utiliza uma implementação sincronizada.

Todas estas validações apenas se destinam a garantir que respeitas as regras de utilização da lista (cuja motivação já mencionei) a um custo mínimo.


Pensa bem antes de colocar em questão as implementações das colecções. Deve-se pensar bem antes de colocar em causa código escrito por Senhores como o Josh Bloch ou o Neal Gafter.


O lado bom da coisa é que existem várias implementações da interface List sem este comportamento asinino, pelo que no código, se só usares as interfaces, é uma solução "drop-in"

Por exemplo aqui http://javolution.org/

Têm uma apresentação interessante aqui:


Pois, a maravilha da framework de colecções ser baseada em interfaces é que podes sempre mudar de implementação com um mínimo de impacto, se bem que eu raramente encontro vantagens significativas.


Cumps.
PJG

Ruben Badaró

unread,
Oct 2, 2008, 6:55:11 AM10/2/08
to ptjug...@googlegroups.com


É possível adicionar elementos à lista recorrendo ao iterador, mas com uma particularidade, as adições (e já agora as remoções) são sempre feitas na posição anterior à do iterador, pelo que o iterador nunca vai ver o elemento adicionado se continuar a interar para a frente, o que provavelmente também não vai ajudar na questão original - quem está a tentar fazer isto em regra está a tentar adicionar elementos à lista para os "apanhar" mais tarde

Obviamente faz todo o sentido que o iterador remova o último elemento retornado, que foi aquele que tiveste a oportunidade de analisar.

A decisão de colocar as adições na posição anterior à do iterador é CORRECTA e prende-se com a preocupação de proporcionar a maior flexibilidade possível.
 
Se fosse ao contrário eras forçado a apanhar os novos registos quer os quisesses processar da mesma forma que os elementos originais da lista ou não.

Assim, não és forçado a processar os novos elementos se não quiseres mas se quiseres tens sempre a solução de utilizar (por exemplo) uma rotina recursiva se (por exemplo) quiseres extrair outros elementos daqueles que estás a processa.

Neste caso é necessário usar o ListIterator e não o Iterator, que não permite adicionar elementos. E mesmo neste caso, tens razão e a inserção na posição anterior também me parece correcta: se quisermos processar o elemento que acabámos de inserir, basta só invocar o método previous() que nos dá o que acabámos de inserir.
 
De resto, sim, também acho este comportamento uma estupidez. Limita a funcionalidade, paga-se um custo de performance por verificar e manter o contador de alterações, e ainda por cima, não serve para o que eventualmente poderia ter interesse, detectar modificações concorrentes em diferentes threads. A lista até pode ter sido alterada e a alteração ao contador não ficar visivel se a thread estiver num cpu diferente. Portanto uma inutilidade pegada, as boas intenções não desculpam um design estúpido.


Estás errado. A implementação que estás a ver NÃO É thread safe.

Penso que o que o Miguel aqui está a dizer é que a colecção, não sendo thread safe, está a fazer verificações sobre as modificações durante a iteração para dar o chamado mecanismo de fail-fast. E este teste é feito com vista ao uso concorrente da colecção, não sendo ela declaradamente thread safe, daí poder ser considerado desnecessário.

Cumps,

Ruben Badaró


PJG

unread,
Oct 2, 2008, 7:38:39 AM10/2/08
to ptjug...@googlegroups.com
Boas,

Pois Ruben, mas essa interpretação é errada. Daí que a implementação não bata certo com as expectativas levantadas pela vossa interpretação.

Do Javadoc do ArrayList:

 * The iterators returned by this class's <tt>iterator</tt> and
 * <tt>listIterator</tt> methods are <i>fail-fast</i>: if list is structurally
 * modified at any time after the iterator is created, in any way except
 * through the iterator's own remove or add methods, the iterator will throw a
 * ConcurrentModificationException.  Thus, in the face of concurrent
 * modification, the iterator fails quickly and cleanly, rather than risking
 * arbitrary, non-deterministic behavior at an undetermined time in the
 * future.<p>

Ou seja, o que se pretende NÃO É validar que não há alterações em múltiplas threads mas SIM validar que todas as alterações à lista são APENAS feitas através dos mecanismos do iterador entre a respectiva criação e o fim da sua utilização, sem, por exemplo, recurso directo aos métodos da lista.


Cumps.,
PJG

On 2008-10-02, at 11:55, Ruben Badaró wrote:
...
 

Manuel Leiria

unread,
Oct 2, 2008, 8:18:29 AM10/2/08
to ptjug...@googlegroups.com
Claro, mas o colega Miguel gosta de falar de boca cheia e mostrar que sabe. Resumindo e concluindo: resposta (simples) à questão (simples) levantada pelo OP passa pelo uso do ListIterator:

//Tenho um conjunto de objectos numa lista
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
list.add("e");
list.add("f");
// Estou a iterá-la.
ListIterator<String> iter = list.listIterator();
while(iter.hasNext()){
/*
* Durante o processo do iterador eu 
* tenho que adicionar mais elementos à lista
*/
if(iter.next().equals("c"))
iter.add("d");
}
for(String str : list){
System.out.println(str);
}

2008/10/2 PJG <paulo....@gmail.com>



--
Manuel Leiria

António Mota

unread,
Oct 2, 2008, 9:06:44 AM10/2/08
to ptjug...@googlegroups.com
Eu nao queria meter-me nisto pq ta-se mm a ver no q isto vai dar, mas

>> Ou seja, o que se pretende NÃO É validar que não há alterações em
>> múltiplas threads mas SIM validar que todas as alterações à lista são APENAS
>> feitas através dos mecanismos do iterador entre a respectiva criação e o fim
>> da sua utilização, sem, por exemplo, recurso directo aos métodos da lista.

Mas isso nao e exactamente a mm coisa?

validar que não há alterações em múltiplas threads

validar que todas as alterações à lista são APENAS feitas através dos
mecanismos do iterador ...


2008/10/2 Manuel Leiria <manuel...@gmail.com>:

Ruben Badaró

unread,
Oct 2, 2008, 9:46:27 AM10/2/08
to ptjug...@googlegroups.com
Boas,

Eu compreendo bem o que diz no javadoc e o propósito que foi dado à excepção, logo tenho as expectativas alinhadas com a realidade.
O que digo é que se tomasse eu a decisão, não tinha fail-fast em classes não thread-safe. Ou seja, o iterador simplesmente iterava pelos elementos da lista existentes no momento da criação do próprio iterador. Poderias ir adicionando e removendo elementos à vontade mas iteravas sempre a lista original.

É mais uma questão de opinião do que de interpretação.


Cumps,

Ruben Badaró

LinkedIn: http://www.linkedin.com/in/rbadaro
Blog: http://www.zonaj.org



2008/10/2 PJG <paulo....@gmail.com>

Miguel Duarte

unread,
Oct 2, 2008, 6:59:10 PM10/2/08
to ptjug...@googlegroups.com
(...)
Convenhamos que o código em Java tb. não é assim tão maior:

List<Integer>     l  = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
Iterator<Integer> it = l.iterator();
while (it.hasNext()) {
System.out.println(it.next());
l.add(9);
}

(...)
 
Pois não, mas acho que esse código só por si não compila :-) Falta a classe, criar um main e os imports etc, compilar, etc. O código Groovy podia ser executado a tal como está ,copy paste para a consola - satisfação imediata!
 
Obviamente faz todo o sentido que o iterador remova o último elemento retornado, que foi aquele que tiveste a oportunidade de analisar.

Pois faz. Eu não criticava o comportamento dos iteradores no que toca à inserção/remoção de elementos na posição anterior. Procurei apenas demonstrar o comportamento dos iteradores para mostrar como funcionavam. 

De resto, sim, também acho este comportamento uma estupidez. Limita a funcionalidade, paga-se um custo de performance por verificar e manter o contador de alterações, e ainda por cima, não serve para o que eventualmente poderia ter interesse, detectar modificações concorrentes em diferentes threads. A lista até pode ter sido alterada e a alteração ao contador não ficar visivel se a thread estiver num cpu diferente. Portanto uma inutilidade pegada, as boas intenções não desculpam um design estúpido.


Estás errado. A implementação que estás a ver NÃO É thread safe.

Eu proprio disse que não é thread safe ao dizer que não serve para detectar alterações em listas concorrentes.


 
Estupidez seria fazer-te pagar o custo de utilizar sincronização numa implementação que te garantem ser livre desse custo. Se queres utilizar uma lista sincronizada, utiliza uma implementação sincronizada.



Todas estas validações apenas se destinam a garantir que respeitas as regras de utilização da lista (cuja motivação já mencionei) a um custo mínimo.

(....)


Pensa bem antes de colocar em questão as implementações das colecções. Deve-se pensar bem antes de colocar em causa código escrito por Senhores como o Josh Bloch ou o Neal Gafter.

Eu não compro argumentos por autoridade, estou-me lascando para quem as fez. Acho que é saudável questionar sempre, se verificar que tenho razão fico na mesma, se verificar que não tinha razão, melhor, é sinal que aprendi qualquer coisa :)

Eu vou tentar listar os meus pontos de discórdia ponto a ponto para evitar confusão e tornar o teu contráditório mais fácil, já que o Josh e o Neal não estão nesta lista para explicar a ideia peregrina que tiveram.

Problema: Não permitir alterações a uma lista, que não pela forma limitada permitida pelo próprio iterado,  enquanto se itera por ela.

- As interfaces indicam o contrato a cumprir. Nas interfaces é dito o comportamento que os métodos devem ter e, quando existem, as respectivas excepções à regra (alguns métodos podem ser de implementação opcional). Diz-me lá, onde é que na documentação das interfaces List, Iterator ou ListIterator este comportamento está documentado?

- Como não está documentado, não vais encontrar. No entanto todas as *implementações* das mesmas interfaces na standard library têm este comportamento e documentam-no. No entanto como o compilador não deixa violar o contracto de forma explicita, lançam uma RuntimeException (tipo não verificado pelo compilador). Para mim é uma forma de violação de contrato.

- Segundo o meu ponto de vista, por violação contratual da interface a coisa já está mal. Mas por vezes a realidade é dura, e temos de pisar o risco de vez em quando. Vamos agora supor que este comportamento até estava documentado nas interfaces. Mesmo assim até poderíamos questionar o mérito da coisa.

Da documentação da ConcurrentModificationException

Começa
"This exception may be thrown by methods that have detected concurrent modification of an object when such modification is not permissible."

Quando é que isto não é possível? Quando estamos a falar de threads diferentes. Dizem eles logo de seguida

For example, it is not generally permissible for one thread to modify a Collection while another thread is iterating over it. In general, the results of the iteration are undefined under these circumstances

Concordo. *nestas circunstâncias*. Em que é que adicionar (1)  elementos ao final (quem diz no final diz noutro lado qualquer) dentro da mesma thread torna os resultados da iteração indefinidos? 

Mais à frente
Note that this exception does not always indicate that an object has been concurrently modified by a different thread. 

Os camaradas, na sua enorme sapiência, queriam detectar alterações concorrentes feitas por threads diferentes para proteger os ignorantes dos programadores de si próprios. Vai daí, em vez de terem classes específicas com este comportamento, em que o acesso é sincronizado e que também continham um contador de alterações (mantido na lista e nos iteradores da mesma), adicionaram o contador às implementação base, fechando o ciclo metendo um wrapper sincronized à volta. 
Perdem os utilizadores que não precisam de partilhar os objectos entre threads:
- em performance
- numa séria limitação de funcionalidade...

Bem... Mas isto tenha de ter um benefício e teve. Com certeza que não foi por acaso. Por isso aqui vai a minha explicação cínica para as coisas terem sido implementadas desta maneira:

Pouparam ter de implementar dezenas de classes e especificar novas interfaces para contratualizar este comportamento, entre as diversas implementações das listas e respectivos iteradores. Isto não é novidade, por algum motivo as classes Vector e Stack foram desenhadas para serem synchronized, obrigado a malta a pagar o preço sem necessidade. A malta tanto esperneou que lá criaram o ArrayList, o mesmo para o StringBuffer vs StringBuilder etc etc. Grandes mestres visionários da aquitectura, sim senhor. Sempre se poupam uns kb no jre é verdade....


Claro que me pode estar a escapar algo pago-te uma caneca se conseguires arranjar outra boa explicação :-)

 
Pois, a maravilha da framework de colecções ser baseada em interfaces é que podes sempre mudar de implementação com um mínimo de impacto, se bem que eu raramente encontro vantagens significativas.

De acordo. Mas podes fazer coisas giras, como ter dezenhas milhões de "objectos" numa Lista implementada num mmap (MappedByteBuffer) sem dar trabalho ao garbage collector, garantido localidade na cache, etc etc etc. Agora acredito que pouca gente vá precisar disto, mas já me deu muito jeito.

MD

Paulo Gaspar

unread,
Oct 2, 2008, 8:40:25 PM10/2/08
to ptjug...@googlegroups.com
Olá Miguel,


Resposta em linha:

On 2008-10-02, at 23:59, Miguel Duarte wrote:

> (...)
> Convenhamos que o código em Java tb. não é assim tão maior:
>
> List<Integer> l = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
> Iterator<Integer> it = l.iterator();
> while (it.hasNext()) {
> System.out.println(it.next());
> l.add(9);
> }
>
> (...)
>
> Pois não, mas acho que esse código só por si não compila :-) Falta a
> classe, criar um main e os imports etc, compilar, etc. O código
> Groovy podia ser executado a tal como está ,copy paste para a
> consola - satisfação imediata!

Vale uma Scrapbook Page do Eclipse?
=;o)

E depois, esse boilerplate code nem tem custo: o IDE gera.


... Sege-se alguma concordância...

E depois...


>
> > Pensa bem antes de colocar em questão as implementações das
> colecções. Deve-se pensar bem antes de colocar em causa código
> escrito por Senhores como o Josh Bloch ou o Neal Gafter.
>
> Eu não compro argumentos por autoridade, estou-me lascando para quem
> as fez. Acho que é saudável questionar sempre, se verificar que
> tenho razão fico na mesma, se verificar que não tinha razão, melhor,
> é sinal que aprendi qualquer coisa :)

Nome não proíbe nada. Facto é facto, lógica é lógica.

Mas uma decisão suspeita por um tipo com (muita) história de ser
consistente deve fazer-nos reflectir um pouco mais antes de lançar a
crítica.

> Eu vou tentar listar os meus pontos de discórdia ponto a ponto para
> evitar confusão e tornar o teu contráditório mais fácil, já que o
> Josh e o Neal não estão nesta lista para explicar a ideia peregrina
> que tiveram.

Não sei se foram só o Josh e o Neal, mas certamente pesaram.

> Problema: Não permitir alterações a uma lista, que não pela forma
> limitada permitida pelo próprio iterado, enquanto se itera por ela.

Queres dizer: pelo próprio "iterador", certo?


>
> - As interfaces indicam o contrato a cumprir. Nas interfaces é dito
> o comportamento que os métodos devem ter e, quando existem, as
> respectivas excepções à regra (alguns métodos podem ser de
> implementação opcional). Diz-me lá, onde é que na documentação das
> interfaces List, Iterator ou ListIterator este comportamento está
> documentado?

O comportamento de ter de fazer as alterações através do iterador?

Está em vários locais da documentação, algo redundantemente.

Podemos começar aqui:
http://java.sun.com/j2se/1.5.0/docs/guide/collections/overview.html
...diz:
> All of the new implementations have fail-fast iterators, which
> detect illegal concurrent modification, and fail quickly and cleanly
> (rather than behaving erratically).

Aqui:
http://java.sun.com/j2se/1.5.0/docs/api/java/util/Iterator.html
...diz:
> Iterators allow the caller to remove elements from the underlying
> collection during the iteration with well-defined semantics.

Aqui:
http://java.sun.com/j2se/1.5.0/docs/api/java/util/Iterator.html#remove()
...diz:
> The behavior of an iterator is unspecified if the underlying
> collection is modified while the iteration is in progress in any way
> other than by calling this method.


==> Ou seja, modifica a colecção apenas através do iterador enquanto o
usas se não queres confusão. E este comentário também deixa a
existência (ou não) da confusão ao critério das implementações.

Aqui:
http://java.sun.com/j2se/1.5.0/docs/api/java/util/ArrayList.html
...diz:
> The iterators returned by this class's iterator and listIterator
> methods are fail-fast: if list is structurally modified at any time
> after the iterator is created, in any way except through the

> iterator's own remove or add methods, the iterator will throw a

> ConcurrentModificationException. Thus, in the face of concurrent

> modification, the iterator fails quickly and cleanly, rather than

> risking arbitrary, non-deterministic behavior at an undetermined
> time in the future.
>
> Note that the fail-fast behavior of an iterator cannot be guaranteed
> as it is, generally speaking, impossible to make any hard guarantees
> in the presence of unsynchronized concurrent modification. Fail-fast
> iterators throwConcurrentModificationException on a best-effort
> basis. Therefore, it would be wrong to write a program that depended
> on this exception for its correctness: the fail-fast behavior of
> iterators should be used only to detect bugs.
>

==> Ou seja, o interface (o contracto) tinha avisado sobre esta
possibilidade e esta implementação apresenta em detalhe as limitações
da mesma no que toca a modificação quando utilizando um iterador.


E de novo aqui:
http://java.sun.com/j2se/1.5.0/docs/api/java/util/ConcurrentModificationException.html
...diz:


> Note that this exception does not always indicate that an object has

> been concurrently modified by a different thread. If a single thread
> issues a sequence of method invocations that violates the contract
> of an object, the object may throw this exception. For example, if a
> thread modifies a collection directly while it is iterating over the
> collection with a fail-fast iterator, the iterator will throw this
> exception.
>
> Note that fail-fast behavior cannot be guaranteed as it is,
> generally speaking, impossible to make any hard guarantees in the
> presence of unsynchronized concurrent modification. Fail-fast
> operations throwConcurrentModificationException on a best-effort
> basis. Therefore, it would be wrong to write a program that depended
> on this exception for its correctness:
> ConcurrentModificationException should be used only to detect bugs.
>

==> O que eu leio desta sequência é que o contracto permite ás
implementações diversas opções e todas as opções que tu descreves
parecem-me respeitar os limites do contracto.

Ou seja, a documentação EXISTE e nos locais certos. (Não está é a néon
ou em fonte tamanho 60.)


> - Como não está documentado, não vais encontrar. No entanto todas as
> *implementações* das mesmas interfaces na standard library têm este
> comportamento e documentam-no. No entanto como o compilador não
> deixa violar o contracto de forma explicita, lançam uma
> RuntimeException (tipo não verificado pelo compilador). Para mim é
> uma forma de violação de contrato.

Bom, já te mostrei que está documentado.

Quanto a RuntimeExceptions, não são um estratagema e sim um mecanismo
estabelecido não só para o caso do iterador como para muitos outros.

Veja-se, por exemplo, outra situação DOCUMENTADA nas listas aqui:
http://java.sun.com/j2se/1.5.0/docs/api/java/util/List.html
temos:
> ... It is not inconceivable that someone might wish to implement a
> list that prohibits duplicates, by throwing runtime exceptions when
> the user attempts to insert them, but we expect this usage to be rare.

E mais adiante:
> Some list implementations have restrictions on the elements that
> they may contain. For example, some implementations prohibit null
> elements, and some have restrictions on the types of their elements.
> Attempting to add an ineligible element throws an unchecked
> exception, typically NullPointerException or ClassCastException.

> - Segundo o meu ponto de vista, por violação contratual da interface
> a coisa já está mal. Mas por vezes a realidade é dura, e temos de
> pisar o risco de vez em quando. Vamos agora supor que este
> comportamento até estava documentado nas interfaces. Mesmo assim até
> poderíamos questionar o mérito da coisa.

MAS ESTÁ DOCUMENTADO, certo?

>
> Da documentação da ConcurrentModificationException
>
> Começa
> "This exception may be thrown by methods that have detected
> concurrent modification of an object when such modification is not
> permissible."
>
> Quando é que isto não é possível? Quando estamos a falar de threads
> diferentes. Dizem eles logo de seguida
>
> For example, it is not generally permissible for one thread to
> modify a Collection while another thread is iterating over it. In
> general, the results of the iteration are undefined under these
> circumstances
>
>
> Concordo. *nestas circunstâncias*. Em que é que adicionar (1)
> elementos ao final (quem diz no final diz noutro lado qualquer)
> dentro da mesma thread torna os resultados da iteração indefinidos?

Depende da implementação subjacente. E creio que já tinhas concordado
lá em cima que adicionar depois da posição corrente é uma MÁ IDEIA, ou
foi impressão minha?

> Mais à frente
> Note that this exception does not always indicate that an object has
> been concurrently modified by a different thread.

Sim, e concluem a explicação. O parágrafo todo:

> Note that this exception does not always indicate that an object has

> been concurrently modified by a different thread. If a single thread
> issues a sequence of method invocations that violates the contract
> of an object, the object may throw this exception. For example, if a
> thread modifies a collection directly while it is iterating over the
> collection with a fail-fast iterator, the iterator will throw this
> exception.

>
> Os camaradas, na sua enorme sapiência, queriam detectar alterações
> concorrentes feitas por threads diferentes para proteger os
> ignorantes dos programadores de si próprios. Vai daí, em vez de
> terem classes específicas com este comportamento, em que o acesso é
> sincronizado e que também continham um contador de alterações
> (mantido na lista e nos iteradores da mesma), adicionaram o contador
> às implementação base, fechando o ciclo metendo um wrapper
> sincronized à volta.
> Perdem os utilizadores que não precisam de partilhar os objectos
> entre threads:
> - em performance
> - numa séria limitação de funcionalidade...

A sério que devias ler isto:
http://java.sun.com/j2se/1.5.0/docs/guide/collections/overview.html

Lê com atenção os "Design Goals".

Os objectivos e compromissos da Collections Framework são bastante
claros:
> The main design goal was to produce an API that was reasonably
> small, both in size, and, more importantly, in "conceptual weight."
> It was critical that the new functionality not seem alien to current
> Java programmers; it had to augment current facilities, rather than
> replacing them. At the same time, the new API had to be powerful
> enough to provide all the advantages described above.
>
> To keep the number of core interfaces small, the interfaces do not
> attempt to capture such subtle distinctions as mutability,
> modifiability, and resizability. Instead, certain calls in the core
> interfaces are optional, allowing implementations to throw an
> UnsupportedOperationException to indicate that they do not support a
> specified optional operation. Of course, collection implementers
> must clearly document which optional operations are supported by an
> implementation.
>


> Bem... Mas isto tenha de ter um benefício e teve. Com certeza que
> não foi por acaso. Por isso aqui vai a minha explicação cínica para
> as coisas terem sido implementadas desta maneira:
>
> Pouparam ter de implementar dezenas de classes e especificar novas
> interfaces para contratualizar este comportamento, entre as diversas
> implementações das listas e respectivos iteradores. Isto não é
> novidade, por algum motivo as classes Vector e Stack foram
> desenhadas para serem synchronized, obrigado a malta a pagar o preço
> sem necessidade. A malta tanto esperneou que lá criaram o ArrayList,
> o mesmo para o StringBuffer vs StringBuilder etc etc. Grandes
> mestres visionários da aquitectura, sim senhor. Sempre se poupam uns
> kb no jre é verdade....

Quando o Java apareceu o meu PC não tinha 16 GB de RAM, e o meu laptop
não tinha 2 GB. A RAM era medida em MB mesmo, e eram poucos. E era só
UMA "core" MESMO.

E se preferes APIs complicadas e com dezenas de especializações, tens
sempre obras de arte (argh!!!) como esta:
http://commons.apache.org/collections/api-release/index.html

...com classes realmente especializadas:
http://commons.apache.org/collections/api-release/org/apache/commons/collections/map/Flat3Map.html

Quanto à concorrência, também no que respeita a colecções, as últimas
versões do Java começam a ter soluções realmente interessantes para
algumas situações:
http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/ConcurrentHashMap.html
http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/ConcurrentLinkedQueue.html


> Claro que me pode estar a escapar algo pago-te uma caneca se
> conseguires arranjar outra boa explicação :-)

Já mereci a caneca?

É certo que a explicação era mesmo a de eles quererem conter a
dimensão e complexidade da API, mas pelo menos demonstrei que eles são
assumidos...

E depois podemos continuar a discussão enquanto eu te pago outra, ok?
=;o)

>
>
> Pois, a maravilha da framework de colecções ser baseada em
> interfaces é que podes sempre mudar de implementação com um mínimo
> de impacto, se bem que eu raramente encontro vantagens significativas.
>
> De acordo. Mas podes fazer coisas giras, como ter dezenhas milhões
> de "objectos" numa Lista implementada num mmap (MappedByteBuffer)
> sem dar trabalho ao garbage collector, garantido localidade na
> cache, etc etc etc. Agora acredito que pouca gente vá precisar
> disto, mas já me deu muito jeito.

Pois, esse tipo de coisa fazia falta à muito tempo. E os primeiros
tipos que eu vi a chorarem por funcionalidade assim foram os meninos
dos parsers XML, nomeadamente estes:
http://xerces.apache.org/xerces2-j/


Have fun,
PJG

PJG

unread,
Oct 3, 2008, 5:10:11 AM10/3/08
to ptjug...@googlegroups.com
Pois, a malta (eu incluido) perde-se em discussões académicas e a resposta prática andava perdida no meio da confusão até o Manuel a resgatar!
=;o)

Cumps.,
PJG

PJG

unread,
Oct 3, 2008, 5:11:46 AM10/3/08
to ptjug...@googlegroups.com
Sim, de facto é isso mesmo o que a documentação diz, como cito noutra
mensagem desta thread.

É que o termo "concurrent" neste caso é entre dois mecanismos e não
entre duas threads.

Cumps.,
PJG

PJG

unread,
Oct 3, 2008, 5:16:28 AM10/3/08
to ptjug...@googlegroups.com
Creio que eles desenharam assim para garantir que, numa utilização comum, o iterador percorre todos os elementos da lista que existiam quando foi criado sem custos extras de implementação (ter de fazer uma cópia, etc.) e sem inconsistências de semântica - ex.: apagar directamente na lista um item que ainda não foi iterado, ao qual existiria referência se o iterador percorresse uma cópia da lista original.

Os problemas são mesmo de concorrência causadora de inconsistências entre dois mecanismos e não entre duas threads.

Cumps.,
PJG

Miguel Duarte

unread,
Oct 3, 2008, 6:23:43 AM10/3/08
to ptjug...@googlegroups.com
Vale uma Scrapbook Page do Eclipse?
=;o)
O código tal como estava tb não ia funcionar num scrapbook. Tens de prefixar todas as classes/interfaces usadas pelo package respectivo. Não podes fazer imports num scrapbook (o que é uma pena)
 
E depois, esse boilerplate code nem tem custo: o IDE gera.
 
É verdade que não se vai gastar uma eternidade de tempo. Mas enquanto estás a teclar no eclipse teclar já eu estou a olhar para os resultados. Não é tão prático.


 

> Problema: Não permitir alterações a uma lista, que não pela forma
> limitada permitida pelo próprio iterado,  enquanto se itera por ela.

Queres dizer: pelo próprio "iterador", certo?
certo


>> - As interfaces indicam o contrato a cumprir. Nas interfaces é dito
> o comportamento que os métodos devem ter e, quando existem, as
> respectivas excepções à regra (alguns métodos podem ser de
> implementação opcional). Diz-me lá, onde é que na documentação das
> interfaces List, Iterator ou ListIterator este comportamento está
> documentado?


O comportamento de ter de fazer as alterações através do iterador?
Não. O que de
 


Está em vários locais da documentação, algo redundantemente.

Podemos começar aqui:
       http://java.sun.com/j2se/1.5.0/docs/guide/collections/overview.html
...diz:
>       All of the new implementations have fail-fast iterators, which
> detect illegal concurrent modification, and fail quickly and cleanly
> (rather than behaving erratically).



 
==> Ou seja, modifica a colecção apenas através do iterador enquanto o

usas se não queres confusão. E este comentário também deixa a
existência (ou não) da confusão ao critério das implementações.

Aqui:
       http://java.sun.com/j2se/1.5.0/docs/api/java/util/ArrayList.html
...diz:
> The iterators returned by this class's iterator and listIterator
> methods are fail-fast: if list is structurally modified at any time
(...)

 
(...)
==> O que eu leio desta sequência é que o contracto permite ás
implementações diversas opções e todas as opções que tu descreves
parecem-me respeitar os limites do contracto.

Ou seja, a documentação EXISTE e nos locais certos. (Não está é a néon
ou em fonte tamanho 60.)
 
Se estás a ver a documentação da classe, de acordo. Eu referia-me à documentação dos interfaces List, ListIterator e Iterator.

 
(...)
>


Bom, já te mostrei que está documentado.

Fico dividido nesta. Para exemplo, a NullPointerException está documentada logo no contrato da interface List e não nas implementações (que podem fazê-lo ou não). A ConcorrentModificationException não. No entanto, como as interfaces fazem referência aos design goals...


Quanto a RuntimeExceptions, não são um estratagema e sim um mecanismo
estabelecido não só para o caso do iterador como para muitos outros.

Veja-se, por exemplo, outra situação DOCUMENTADA nas listas aqui:
temos:
> ... It is not inconceivable that someone might wish to implement a
(...)> exception, typically NullPointerException or ClassCastException.

Certo, mas se reparares os throws destas excepções estão no interface List. A ConcurrentModificationException *não* está no interface Iterator ou ListIterador.


 
MAS ESTÁ DOCUMENTADO, certo?
Nas implementações e nos design goals sim. Nos interfaces que os iteradores dizem que implementam não.

Depende da implementação subjacente. E creio que já tinhas concordado
lá em cima que adicionar depois da posição corrente é uma MÁ IDEIA, ou
foi impressão minha?
 
Não. Isso foste tu que disseste :-) Para mim acharia perfeitamente legítimo, por exemplo, adicionar elementos ao final da lista enquanto se itera nela.


 
       http://java.sun.com/j2se/1.5.0/docs/guide/collections/overview.html

Lê com atenção os "Design Goals".

(...)
não tinha 2 GB. A RAM era medida em MB mesmo, e eram poucos. E era só
UMA "core" MESMO.
Pois.


> Claro que me pode estar a escapar algo pago-te uma caneca se
> conseguires arranjar outra boa explicação :-)

Já mereci a caneca?
Já  :-)
 
É certo que a explicação era mesmo a de eles quererem conter a
dimensão e complexidade da API, mas pelo menos demonstrei que eles são
assumidos...

E depois podemos continuar a discussão enquanto eu te pago outra, ok?
=;o)

Não sei se o nível da discussão se vai manter ao mesmo nível depois das canecas :)


Miguel Duarte

unread,
Oct 3, 2008, 8:56:26 AM10/3/08
to ptjug...@googlegroups.com
2008/10/2 Manuel Leiria <manuel...@gmail.com>
Claro, mas o colega Miguel gosta de falar de boca cheia e mostrar que sabe.
 
A ideia de participar é  discutir o meu ponto de vista sem medo de mais tarde vir a reconhecer que disse algo de errado ou mostrar ignorância (algo que não tenho problema nenhum em admitir publicamente). Isto não tem nada a ver com o "mostrar que sabe". Como dizia, de um argumento civilizado ou fico na mesma ou aprendo qualquer coisa. Nunca se sai a perder.

 
Resumindo e concluindo: resposta (simples) à questão (simples) levantada pelo OP passa pelo uso do ListIterator:
(...)
Certo. De facto a questão do OP era apenas adicionar à lista e assumi que ele queria adicionar ao fim da lista enquanto iterava por ela usando um iterador. Isto não é possível, o comportamento está documentado, já engoli o sapo à muitos anos.

Para mim o conceito de iterador é o de iterar pela lista, elemento a elemento tal como está na interface Iterator e ListIterator. Numa situação em que a lista não é partilhada por várias threads, adicionar elementos à lista não deveria impedir o iterador de fazer o seu trabalho. Existe algum? Agradecia exemplo se possível.

Na remoção existe o risco de o iterador ficar inválido (estar a referenciar um elemento que já não faz parte da lista), invalidando o contrato da interface (i.e. i.hasNext() retorna verdadeiro e no momento em que se invoca o next() já não está lá)

MS






PJG

unread,
Oct 3, 2008, 10:56:42 AM10/3/08
to ptjug...@googlegroups.com
> É verdade que não se vai gastar uma eternidade de tempo. Mas
enquanto estás a teclar no eclipse teclar já eu estou a olhar para os
resultados. Não é tão prático.

Hummm.... um dia destes combinamos uma corrida.

Apesar de eu acabar com mais texto, duvido que carregue mais vezes nas
teclas.


> Se estás a ver a documentação da classe, de acordo. Eu referia-me à
documentação dos interfaces List, ListIterator e Iterator.

Se releres o meu texto original verás que os interfaces deixam
explicitamente em aberto o comportamento que as implementações seguem
(e documentam).


Falta definir a logística para as tais canecas....


Cumps.,
PJG
> (...)==> O que eu leio desta sequência é que o contracto permite ás
> (...)não tinha 2 GB. A RAM era medida em MB mesmo, e eram poucos. E
Reply all
Reply to author
Forward
0 new messages