Por que o Java 8 não inclui coleções imutáveis?

123

A equipe de Java fez um grande trabalho removendo barreiras para a 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 transmissão muito rápidas. Considerando o quão bom trabalho eles fizeram ao adicionar funções de primeira classe e métodos funcionais em coleções, por que eles falharam completamente em 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 que são as mesmas que as mutáveis, menos os métodos "set" e fazer com que as interfaces existentes sejam estendidas a partir deles,

                  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 dada para indicar se a operação foi bem-sucedida ou falhou. Coleções imutáveis teriam que tratar tais métodos como fábricas e retornar uma nova coleção contendo o elemento adicionado - o que é incompatível com a assinatura atual. Mas isso poderia ser contornado usando um nome de método diferente como ImmutableList.append () ou .addAt () e ImmutableMap.putEntry (). A verbosidade resultante seria mais do que superada pelos benefícios de se trabalhar com coleções imutáveis, e o sistema de tipos evitaria erros ao 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 mudam.
  • Documentação - se um método tiver uma interface de coleção imutável, você sabe que não modificará essa coleção. Se um método retorna uma coleção imutável, você sabe que não pode modificá-lo.
  • Concorrência - as coleções imutáveis podem ser compartilhadas com segurança entre os segmentos.

Como alguém que provou linguagens que assumem a imutabilidade, é muito difícil voltar ao Oeste Selvagem da mutação desenfreada. As coleções do Clojure (abstração de sequência) já possuem tudo o que as coleções do Java 8 fornecem, além da imutabilidade (embora talvez usando memória e tempo extras devido a listas vinculadas sincronizadas em vez de fluxos). O Scala possui coleções mutáveis e imutáveis com um conjunto completo de operações e, embora essas operações sejam ávidas, chamar o .iterator dá uma visão preguiçosa (e há outras maneiras de avaliá-las preguiçosamente). Não vejo como o Java pode continuar a competir sem coleções imutáveis.

Alguém pode me indicar a história ou a discussão sobre isso? Certamente é público em algum lugar.

    
por GlenPeterson 18.12.2013 / 15:53
fonte

6 respostas

109

Porque as coleções imutáveis exigem que o compartilhamento seja utilizável. Caso contrário, todas as operações descartam uma outra lista inteira no heap em algum lugar. Idiomas que são totalmente imutáveis, como o Haskell, geram quantidades surpreendentes de lixo sem otimizações e compartilhamento agressivos. Não é recomendável colocar a coleção que só pode ser usada com elementos < 50 na biblioteca padrão.

Mais ainda, coleções imutáveis muitas vezes têm implementações fundamentalmente diferentes de suas contrapartes mutáveis. Considere, por exemplo, ArrayList , um ArrayList imutável e eficiente não seria um array! Deve ser implementado com uma árvore balanceada com um grande fator de ramificação, o Clojure usa 32 IIRC. Tornar as coleções mutáveis "imutáveis" apenas adicionando uma atualização funcional é um erro 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 você ficaria um pouco irritado se pudesse modificar um elemento em uma lista, e perceber que você acabou de modificar um elemento nas outras 20 versões da lista que você tinha.

Isso também descarta classes enormes de otimizações vitais para a imutabilidade eficiente, compartilhamento, fluxo de fusão, o nome dele, a mutabilidade o quebra. (Isso seria um bom slogan para os evangelistas da FP)

    
por 18.12.2013 / 17:38
fonte
66

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 se confundir, apesar de significarem três coisas diferentes.

  • Uma classe base de coleção legível ou um tipo de interface promete que alguém 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 lançar ou manipulá-lo de tal forma. maneira de permitir a modificação.

  • Uma interface de coleção somente leitura não inclui novos membros, mas deve ser implementada apenas por uma classe que prometa 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 que possa fazer isso. Não promete, no entanto, que a coleção não será modificada por outra coisa que tenha uma referência aos internos. Observe que uma interface de coleção somente leitura pode não conseguir 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 terá 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 está quebrada.

Às vezes é útil ter tipos de coleta mutáveis e imutáveis strongmente associados que implementam ou derivam do mesmo tipo "legível" e que o tipo legível inclua os métodos AsImmutable , AsMutable e AsNewMutable . Esse design pode permitir o código que deseja persistir os dados em uma coleção para chamar AsImmutable ; esse método fará uma cópia defensiva se a coleção for mutável, mas pule a cópia se ela já for imutável.

    
por 21.12.2013 / 09:37
fonte
13

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 no classe java.util.Collections :

Como alguém apontou nos comentários para a 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 incluído ou removido de tal coleção), os objetos reais referenciados pela coleção pode ser modificada 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áveis. Se o tipo permite que seus objetos sejam mutados, então 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, então os membros de uma coleção devem ser de um tipo imutável.

    
por 25.12.2013 / 22:44
fonte
7

Esta é uma ótima pergunta. Eu gosto de ter a idéia de que, de todo o código escrito em java e rodando em milhões de computadores em todo o mundo, todo dia, 24 horas, cerca de metade do clock total deve ser desperdiçado fazendo nada além de cópias seguras de coleções. sendo retornado por funções. (E coleta de lixo essas coleções milissegundos após sua criação).

Uma porcentagem de programadores java está ciente da existência da família de métodos unmodifiableCollection() da classe Collections , mas mesmo entre eles muitos não se importam com isso.

E eu não posso culpá-los: uma interface que finge ser de leitura-escrita, mas vai lançar um UnsupportedOperationException se você cometer o erro de invocar qualquer um dos seus métodos de 'escrita' é uma coisa bastante malvada!

Agora, uma interface como Collection que estaria faltando os métodos add() , remove() e clear() não seria uma interface "ImmutableCollection"; seria uma interface "UnmodifiableCollection". Na verdade, nunca poderia haver uma interface "ImmutableCollection", porque a 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 forneça essa 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 ela é implementada, então a resposta tem que ser um não : por tudo o que você sabe, pode ser um não modificável (para você) a visão de uma coleção que é de fato mutável, (assim como o que você obtém com Collections.unmodifiableCollection() ,) então tentar ler a partir dela enquanto outra thread está modificando isso resultaria na leitura de dados corrompidos.

Então, 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" simplesmente significa que quem quer que tenha uma referência a essa interface é impedido de modificar a coleção subjacente e ela é impedida simplesmente porque a interface não possui métodos de modificação, não porque a coleção subjacente é 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 aulas de coleção imutáveis teriam que ser finais, de modo que, quando você receber uma referência a essa coleção, tenha certeza de que ela se comportará como uma coleção imutável, não importa o que você ou qualquer outra pessoa que tenha uma referência a ela. isso pode fazer com isso.

Assim, para ter um conjunto completo de coleções em java (ou qualquer outro idioma imperativo declarativo), precisaríamos do seguinte:

  1. Um conjunto de unmodifiable coleção interfaces .

  2. Um conjunto de mutáveis coleções interfaces , estendendo as não modificáveis.

  3. Um conjunto de mutáveis coleções classes implementando as interfaces mutáveis e, por extensão, também as interfaces não modificáveis.

  4. Um conjunto de imutáveis coleções classes , implementando as interfaces não modificáveis, mas que geralmente são transmitidas como classes, para garantir a imutabilidade.

Implementei todos os itens acima para me divertir, e estou usando-os em projetos e eles funcionam como um encanto.

A razão pela qual eles não fazem parte do Java Runtime é 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ária é um conjunto de interfaces mutáveis & aulas para imutabilidade estrutural . (Que pode simplesmente ser chamado de "Rígido" porque o prefixo "StructurallyImmutable" é muito longo).

    
por 04.06.2015 / 17:07
fonte
2
Coleções imutáveis podem ser profundamente recursivas, comparadas umas com as outras, e não são injustificadamente ineficientes se a igualdade de objetos for por secureHash. Isso é chamado de floresta de merkle. Pode ser por coleção ou dentro de partes deles, como uma árvore AVL (auto-balanceamento binário) para um mapa ordenado.

A menos que todos os objetos java nessas coleções tenham um id exclusivo ou algum bitstring para hash, a coleção não tem nada para hash para nomear-se exclusivamente.

Exemplo: No meu laptop 4x1.6ghz, posso rodar 200K sha256s por segundo do menor tamanho que se encaixa em um ciclo hash (até 55 bytes), comparado a 500K HashMap ops ou 3M ops em uma hashtable de longs. As novas coleções de 200K / log (collectionSize) 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.

    
por 04.09.2016 / 21:52
fonte
-3

Desempenho. 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 encadeamentos em execução, eles podem querer obter a versão atual da coleção e não a versão que foi passada 12 horas atrás, quando o encadeamento foi iniciado.

Armazenamento. Com objetos imutáveis em um ambiente multi-threaded 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 de calendário ou data, mas quando é uma coleção de 10.000 widgets isso vai te matar.

    
por 19.12.2013 / 02:15
fonte