A equipe do Java fez um grande trabalho removendo barreiras à programação funcional no Java 8. Em particular, as alterações nas coleções java.util fazem um ótimo trabalho de encadear transformações em operações de fluxo muito rápido. Considerando o bom trabalho que fizeram ao adicionar funções de primeira classe e métodos funcionais nas coleções, por que eles falharam completamente ao fornecer coleções imutáveis ou até mesmo interfaces de coleção imutáveis?
Sem alterar nenhum código existente, a equipe Java pode, a qualquer momento, adicionar interfaces imutáveis iguais às mutáveis, menos os métodos "set" e fazer com que as interfaces existentes se estendam a partir deles, assim:
ImmutableIterable
____________/ |
/ |
Iterable ImmutableCollection
| _______/ / \ \___________
| / / \ \
Collection ImmutableList ImmutableSet ImmutableMap ...
\ \ \_________|______________|__________ |
\ \___________|____________ | \ |
\___________ | \ | \ |
List Set Map ...
Claro, operações como List.add () e Map.put () atualmente retornam um valor booleano ou anterior para a chave especificada para indicar se a operação foi bem-sucedida ou falhou. Coleções imutáveis teriam que tratar métodos como fábricas e retornar uma nova coleção contendo o elemento adicionado - que é incompatível com a assinatura atual. Mas isso pode ser contornado usando um nome de método diferente, como ImmutableList.append () ou .addAt () e ImmutableMap.putEntry (). A verbosidade resultante seria mais do que compensada pelos benefícios de trabalhar com coleções imutáveis, e o sistema de tipos impediria erros de chamar o método errado. Com o tempo, os métodos antigos podem ser preteridos.
Vitórias de coleções imutáveis:
- Simplicidade - o raciocínio sobre o código é mais simples quando os dados subjacentes não são alterados.
- Documentação - se um método usa uma interface de coleção imutável, você sabe que não vai modificar essa coleção. Se um método retorna uma coleção imutável, você sabe que não pode modificá-lo.
- Simultaneidade - coleções imutáveis podem ser compartilhadas com segurança entre threads.
Como alguém que experimentou idiomas que assumem imutabilidade, é muito difícil voltar ao oeste selvagem de uma mutação desenfreada. As coleções de Clojure (abstração de sequência) já possuem tudo o que as coleções do Java 8 fornecem, além de imutabilidade (embora talvez usando memória e tempo extras devido a listas vinculadas sincronizadas em vez de fluxos). O Scala tem coleções mutáveis e imutáveis com um conjunto completo de operações e, embora essas operações sejam ágeis, chamar o .iterator oferece uma visão lenta (e existem outras maneiras de avaliá-las). Não vejo como o Java pode continuar competindo sem coleções imutáveis.
Alguém pode me apontar para a história ou discussão sobre isso? Certamente é público em algum lugar.
fonte
const
coleçõesCollections.unmodifiable*()
. mas não os trate como imutáveis quando não sãoImmutableList
nesse diagrama, as pessoas podem passar um mutávelList
? Não, isso é uma violação muito ruim do LSP.Respostas:
Porque coleções imutáveis exigem absolutamente que o compartilhamento seja utilizável. Caso contrário, todas as operações soltarão uma lista totalmente diferente em algum lugar. Idiomas totalmente imutáveis, como Haskell, geram quantidades surpreendentes de lixo sem otimizações e compartilhamento agressivos. Ter uma coleção que só pode ser usada com <50 elementos não vale a pena colocar na biblioteca padrão.
Além disso, coleções imutáveis geralmente têm implementações fundamentalmente diferentes das de suas contrapartes mutáveis. Considere, por exemplo
ArrayList
, um imutável eficienteArrayList
não seria uma matriz! Ele deve ser implementado com uma árvore equilibrada com um grande fator de ramificação, Clojure usa 32 IIRC. Tornar coleções mutáveis "imutáveis" ao adicionar apenas uma atualização funcional é um bug de desempenho tanto quanto um vazamento de memória.Além disso, o compartilhamento não é viável em Java. Java fornece muitos ganchos irrestritos à mutabilidade e à igualdade de referência para tornar o compartilhamento "apenas uma otimização". Provavelmente seria um pouco irritante se você pudesse modificar um elemento em uma lista e percebesse que você acabou de modificar um elemento nas outras 20 versões da lista que você possui.
Isso também exclui enormes classes de otimizações vitais para imutabilidade eficiente, compartilhamento, fusão de fluxo, o que você quiser, a mutabilidade o interrompe. (Isso seria um bom slogan para os evangelistas da PF)
fonte
String
para umStringBuffer
, manipulá-los e copiá-los para um novo imutávelString
. Usar esse padrão com conjuntos e listas pode ser tão bom quanto usar tipos imutáveis que são projetados para facilitar a produção de instâncias ligeiramente alteradas, mas ainda podem ser melhores ...Uma coleção mutável não é um subtipo de uma coleção imutável. Em vez disso, coleções mutáveis e imutáveis são descendentes irmãos de coleções legíveis. Infelizmente, os conceitos de "legível", "somente leitura" e "imutável" parecem ficar confusos, apesar de terem três significados diferentes.
Uma classe base de coleção ou tipo de interface legível promete que se pode ler itens e não fornece nenhum meio direto de modificar a coleção, mas não garante que o código que recebe a referência não possa convertê-la ou manipulá-la de maneira a permitir a modificação.
Uma interface de coleção somente leitura não inclui nenhum membro novo, mas deve ser implementada apenas por uma classe que promete que não há como manipular uma referência a ela de forma a alterar a coleção nem receber uma referência a algo isso poderia fazê-lo. No entanto, não promete que a coleção não seja modificada por outra coisa que tenha uma referência aos internos. Observe que uma interface de coleção somente leitura pode não ser capaz de impedir a implementação por classes mutáveis, mas pode especificar que qualquer implementação ou classe derivada de uma implementação que permita a mutação seja considerada uma implementação "ilegítima" ou derivada de uma implementação .
Uma coleção imutável é aquela que sempre conterá os mesmos dados, desde que exista qualquer referência a ela. Qualquer implementação de uma interface imutável que nem sempre retorna os mesmos dados em resposta a uma solicitação específica é interrompida.
Às vezes é útil ter tipos fortemente associados mutáveis e imutáveis de cobrança que ambas implementam ou derivam do mesmo tipo "legível", e têm o tipo legível incluem
AsImmutable
,AsMutable
eAsNewMutable
métodos. Esse design pode permitir que o código que deseja persistir os dados em uma coleção chameAsImmutable
; esse método fará uma cópia defensiva se a coleção for mutável, mas pulará a cópia se já estiver imutável.fonte
int
, com métodosgetLength()
egetItemAt(int)
.ReadableIndexedIntSequence
, poderia-se produzir uma instância de um tipo imutável suportado por matriz, copiando todos os itens em uma matriz, mas suponha que uma implementação específica simplesmente retornasse 16777216 para comprimento e((long)index*index)>>24
para cada item. Seria uma sequência imutável legítima de números inteiros, mas copiá-la para uma matriz seria um enorme desperdício de tempo e memória.asImmutable
uma instância da sequência acima definida versus o custo de construção uma cópia imutável baseada em array]. Eu diria que ter interfaces definidas para esses fins é provavelmente melhor do que tentar usar abordagens ad-hoc; IMHO, a maior razão ...O Java Collections Framework fornece a capacidade de criar uma versão somente leitura de uma coleção por meio de seis métodos estáticos na classe java.util.Collections :
Como alguém apontou nos comentários da pergunta original, as coleções retornadas podem não ser consideradas imutáveis porque, embora as coleções não possam ser modificadas (nenhum membro pode ser adicionado ou removido de uma coleção), os objetos reais referenciados pela coleção pode ser modificado se o tipo de objeto permitir.
No entanto, esse problema permaneceria independentemente de o código retornar um único objeto ou uma coleção de objetos não modificável. Se o tipo permitir que seus objetos sejam alterados, essa decisão foi tomada no design do tipo e não vejo como uma alteração no JCF poderia alterar isso. Se a imutabilidade é importante, os membros de uma coleção devem ser de um tipo imutável.
fonte
immutableList
etc. métodos de fábrica que retornariam um invólucro somente leitura em torno de uma cópia de um pass-in lista , a menos que a lista passada já seja imutável . Seria fácil criar tipos definidos pelo usuário como esse, mas com um problema: não haveria maneira dojoesCollections.immutableList
método reconhecer que não seria necessário copiar o objeto retornado porfredsCollections.immutableList
.Esta é uma pergunta muito boa. Gosto de ter a ideia de que, de todo o código escrito em java e executado em milhões de computadores em todo o mundo, todos os dias, dia e noite, cerca de metade do total de ciclos de relógio deve ser desperdiçada sem fazer nada além de fazer cópias de segurança de coleções que são sendo retornado por funções. (E coleta de lixo dessas coleções milissegundos após sua criação.)
Uma porcentagem dos programadores de Java está ciente da existência da
unmodifiableCollection()
família de métodos daCollections
classe, mas mesmo entre eles, muitos simplesmente não se preocupam com isso.E não posso culpá-los: uma interface que finge ser de leitura e gravação, mas gera um
UnsupportedOperationException
se você cometer o erro de invocar qualquer um de seus métodos de 'gravação', é uma coisa muito ruim de se ter!Agora, uma interface como a
Collection
qual estaria faltando aadd()
,remove()
eclear()
métodos não seria uma interface "ImmutableCollection"; seria uma interface "UnmodifiableCollection". Por uma questão de fato, nunca poderia haver uma interface "ImmutableCollection", porque imutabilidade é uma natureza de uma implementação, não uma característica de uma interface. Eu sei, isso não é muito claro; deixe-me explicar.Suponha que alguém lhe entregue uma interface de coleção somente leitura; é seguro passá-lo para outro segmento? Se você soubesse com certeza que isso representa uma coleção verdadeiramente imutável, a resposta seria "sim"; infelizmente, como é uma interface, você não sabe como é implementada; portanto, a resposta deve ser um não : pelo que você sabe, pode ser uma visão não modificável (para você) de uma coleção que é de fato mutável, (como o que você acompanha
Collections.unmodifiableCollection()
), ao tentar ler a partir dele enquanto outro segmento está modificando, resultaria na leitura de dados corrompidos.Portanto, o que você descreveu essencialmente é um conjunto de interfaces de coleção não "Imutáveis", mas "Não Modificáveis". É importante entender que "Unmodifiable" significa simplesmente que quem tem uma referência a essa interface é impedido de modificar a coleção subjacente e eles são impedidos simplesmente porque a interface carece de métodos de modificação, não porque a coleção subjacente seja necessariamente imutável. A coleção subjacente pode muito bem ser mutável; você não tem conhecimento e não tem controle sobre isso.
Para ter coleções imutáveis, elas teriam que ser classes , não interfaces!
Essas classes de coleções imutáveis teriam que ser finais, para que, quando você receber uma referência a essa coleção, saiba com certeza que ela se comportará como uma coleção imutável, independentemente do que você ou qualquer outra pessoa que faça referência a ela possa faça com isso.
Portanto, para ter um conjunto completo de coleções em java (ou qualquer outra linguagem imperativa declarativa), precisaríamos do seguinte:
Um conjunto de interfaces de coleção não modificáveis .
Um conjunto de interfaces de coleção mutáveis , estendendo as não modificáveis.
Um conjunto de classes de coleção mutável implementando as interfaces mutáveis e, por extensão, também as interfaces não modificáveis.
Um conjunto de classes de coleção imutáveis , implementando interfaces não modificáveis, mas geralmente transmitidas como classes, para garantir a imutabilidade.
Eu implementei todas as opções acima por diversão, e as estou usando em projetos, e elas funcionam como um encanto.
A razão pela qual eles não fazem parte do tempo de execução do java é provavelmente porque se pensava que isso seria muito / muito complexo / muito difícil de entender.
Pessoalmente, acho que o que descrevi acima não é suficiente; mais uma coisa que parece ser necessário é um conjunto de interfaces mutáveis e classes para imutabilidade estrutural . (Que pode ser chamado simplesmente de "Rígido" porque o prefixo "Estruturalmente Imutável" é muito longo).
fonte
List<T> add(T t)
- todos os métodos "mutadores" devem retornar uma nova coleção que reflita a alteração. 2. Para o bem ou para o mal, as interfaces geralmente representam um contrato além de uma assinatura. Serializable é uma dessas interfaces. Da mesma forma, o Comparable exige que você implemente corretamente seucompareTo()
método para funcionar corretamente e, idealmente, seja compatível comequals()
ehashCode()
.add()
. Mas suponho que se os métodos mutadores fossem adicionados às classes imutáveis, eles precisariam retornar também classes imutáveis. Portanto, se houver um problema oculto lá, eu não o vejo.Suppose someone hands you such a read-only collection interface; is it safe to pass it to another thread?
Suponha que alguém passe uma instância de uma interface de coleção mutável. É seguro invocar algum método nele? Você não sabe que a implementação não se repete para sempre, gera uma exceção ou ignora completamente o contrato da interface. Por que ter um padrão duplo especificamente para coleções imutáveis?SortedSet
subclasse do conjunto com uma implementação não conforme. Ou passando um inconsistenteComparable
. Quase tudo pode ser quebrado, se você quiser. Eu acho que é isso que @Doval quis dizer com "padrões duplos".Coleções imutáveis podem ser profundamente recursivas, comparadas umas com as outras, e não excessivamente ineficientes se a igualdade de objetos for por secureHash. Isso é chamado de floresta merkle. Pode ser por coleção ou em partes deles, como uma árvore AVL (binária com auto balanceamento) para um mapa classificado.
A menos que todos os objetos java nessas coleções tenham um ID exclusivo ou alguma cadeia de bits para hash, a coleção não terá nada para hash para nomear-se exclusivamente.
Exemplo: No meu laptop 4x1,6ghz, posso executar 200K sha256s por segundo do menor tamanho que se encaixa em 1 ciclo de hash (até 55 bytes), em comparação com 500K HashMap ops ou 3M ops em uma hashtable de longos. 200K / log (collectionSize) novas coleções por segundo são rápidas o suficiente para algumas coisas em que a integridade dos dados e a escalabilidade global anônima são importantes.
fonte
Atuação. As coleções por natureza podem ser muito grandes. Copiar 1000 elementos para uma nova estrutura com 1001 elementos em vez de inserir um único elemento é simplesmente horrível.
Concorrência. Se você tiver vários threads em execução, poderá obter a versão atual da coleção e não a versão passada 12 horas atrás quando o thread foi iniciado.
Armazenamento. Com objetos imutáveis em um ambiente multiencadeado, você pode acabar com dezenas de cópias do "mesmo" objeto em diferentes pontos do seu ciclo de vida. Não importa para um objeto Calendário ou Data, mas quando for uma coleção de 10.000 widgets, isso o matará.
fonte