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 compactol = [1,2,3,4,5,6,7,8]it = l.listIterator()while(it.hasNext()) {println it.next()l.add(9)}1Exception thrown: java.util.ConcurrentModificationException
java.util.ConcurrentModificationExceptionat 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
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:
É 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 tardeObviamente 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.
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.
...
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);}
(...)
Obviamente faz todo o sentido que o iterador remova o último elemento retornado, que foi aquele que tiveste a oportunidade de analisar.
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.
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.
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.
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
Vale uma Scrapbook Page do Eclipse?
=;o)
E depois, esse boilerplate code nem tem custo: o IDE gera.
Queres dizer: pelo próprio "iterador", certo?
> 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.
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).
==> 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.)
(...)
>
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:
temos:
> ... It is not inconceivable that someone might wish to implement a
(...)> exception, typically NullPointerException or ClassCastException.
MAS ESTÁ DOCUMENTADO, certo?
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?
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.
> Claro que me pode estar a escapar algo pago-te uma caneca seJá mereci a caneca?
> conseguires arranjar outra boa explicação :-)
É 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)
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: