Olá pessoal!
Hoje, sua atenção é convidada para a tradução de um artigo cuidadosamente escrito sobre um dos problemas básicos do Java - a mutabilidade e como isso afeta a estrutura das estruturas de dados e o trabalho com eles. O material foi retirado do blog de Nicolai Parlog, cujo brilhante estilo literário realmente tentamos manter em tradução. O próprio Nicholas é notavelmente caracterizado por um
trecho do blog da empresa JUG.ru em Habré; vamos citar esta passagem em sua totalidade:

Nikolay Parlog é um cara da mídia que faz análises sobre os recursos Java. Mas ele não é da Oracle ao mesmo tempo, então as avaliações são surpreendentemente francas e compreensíveis. Às vezes, depois deles, alguém é demitido, mas raramente. Nikolay irá falar sobre o futuro do Java, o que estará na nova versão. Ele é bom em falar sobre tendências e geralmente sobre o grande mundo. Ele é um companheiro muito lido e erudito. Até relatórios simples são agradáveis de ouvir, o tempo todo que você aprende algo novo. Além disso, Nicholas sabe além do que está dizendo. Ou seja, você pode acessar qualquer relatório e apenas apreciá-lo, mesmo que esse não seja o seu tópico. Ele está ensinando. Ele escreveu "O Java Module System" para a Manning Publishing House, mantém blogs sobre desenvolvimento de software no codefx.org e está envolvido há vários projetos de código aberto. Ele pode ser contratado na conferência, é freelancer. É verdade que um freelancer muito caro. Aqui está o relatório .
Nós lemos e votamos. Quem gosta especialmente da postagem - também recomendamos que você analise os comentários do leitor na postagem original.
Volatilidade é ruim, certo? Assim, a
imutabilidade é boa . As principais estruturas de dados nas quais a imutabilidade é especialmente proveitosa são as coleções: em Java, é uma lista (
List
), um conjunto (
Set
) e um dicionário (
Map
). No entanto, embora o JDK venha com coleções imutáveis (ou não modificáveis?), O sistema de tipos não sabe nada sobre isso. Não há
ImmutableList
no JDK, e esse tipo de Guava parece completamente inútil para mim. Mas porque? Por que não adicionar
Immutable...
a essa mistura e não dizer que deveria ser?
O que é uma coleção imutável?
Na terminologia do JDK, os significados das palavras "
imutável " e "
não modificável " mudaram nos últimos anos. Inicialmente, “não modificável” era chamada de instância que não permitia mutabilidade (mutabilidade): em resposta à mudança de métodos, ele lançou
UnsupportedOperationException
. No entanto, poderia ser alterado de outra maneira - talvez porque fosse apenas um invólucro em torno de uma coleção mutável. Essas visualizações são refletidas nos métodos
Collections::unmodifiableList
,
unmodifiableSet
e
unmodifiableMap
, bem como em
seu JavaDoc .
Inicialmente, o termo "
imutável " refere-se a coleções retornadas pelos
métodos de fábrica das coleções Java 9 . As coleções em si não puderam ser alteradas de forma alguma (sim, há
reflexão , mas não conta); portanto, parece que justificam seu nome. Infelizmente, muitas vezes surge confusão por causa disso. Suponha que exista um método que exiba todos os elementos de uma coleção imutável na tela - sempre dará o mesmo resultado? Hein? Ou não?
Se você não respondeu imediatamente
não , significa que você acabou de ver exatamente qual confusão é possível aqui. Uma "
coleção imutável de agentes secretos " - parece muito semelhante a uma "
coleção imutável de agentes secretos imutáveis "
, mas essas duas entidades podem não ser idênticas. Uma coleção imutável não pode ser editada usando operações de inserção / exclusão / limpeza, etc., mas se os agentes secretos são mutáveis (embora os traços de caractere nos filmes de espionagem sejam tão ruins que eles realmente não acreditam), isso não significa que isso que toda a coleção de agentes secretos é imutável. Portanto, agora há uma mudança no sentido de nomear essas coleções como
não modificáveis , e não
imutáveis , o que é consagrado na
nova edição do JavaDoc .
As coleções imutáveis discutidas neste artigo podem conter elementos mutáveis.
Pessoalmente, não gosto de uma revisão dessa terminologia. Na minha opinião, o termo "coleção imutável" deve significar apenas que a coleção em si não pode sofrer alterações, mas não deve caracterizar os elementos nela contidos. Nesse caso, há mais um ponto positivo: o termo “imutabilidade” no ecossistema Java não se transforma em completa bobagem.
De uma forma ou de outra, neste artigo falaremos sobre
coleções imutáveis , onde ...
- As instâncias contidas na coleção são determinadas no estágio de design
- Essas cópias - uma quantidade uniforme, nem reduzem nem adicionam
- Nenhuma declaração é feita sobre a mutabilidade desses elementos.
Vamos insistir no fato de que agora praticaremos a adição de coleções imutáveis. Para ser preciso - uma lista imutável. Tudo o que será dito sobre listas é igualmente aplicável a coleções de outros tipos.
Começando a adicionar coleções imutáveis!
Vamos criar a interface
ImmutableList
e torná-la, em relação à
List
, uh ... o quê? Supertipo ou subtipo? Vamos nos debruçar sobre a primeira opção.

Lindamente, o
ImmutableList
não
ImmutableList
métodos
ImmutableList
, portanto, usá-lo é sempre seguro, certo? Então ?! Não senhor.
List<Agent> agents = new ArrayList<>();
Este exemplo mostra que é possível transferir uma lista não totalmente imutável para a API, cuja operação pode ser baseada na imutabilidade e, portanto, anular todas as garantias que um nome desse tipo pode sugerir. Aqui está uma receita para você que pode levar ao desastre.
OK,
ImmutableList
estende a
List
. Talvez?

Agora, se a API espera uma lista imutável, ela receberá essa lista, mas há duas desvantagens:
- As listas imutáveis ainda devem oferecer métodos de modificação (conforme definidos no supertipo), e a única implementação possível gerará uma exceção
ImmutableList
instâncias ImmutableList
também ImmutableList
instâncias de List
e, quando atribuídas a uma variável, transmitidas como argumento ou retornadas desse tipo, é lógico supor que a mutabilidade seja permitida.
Assim, acontece que você só pode usar o
ImmutableList
localmente, porque ele passa as bordas da API como uma
List
, o que requer um nível de precaução sobre-humano ou explode em tempo de execução. Não é tão ruim quanto uma
List
estendendo uma
List
ImmutableList
, mas essa solução ainda está longe do ideal.
Isso é exatamente o que eu tinha em mente quando disse que o tipo
ImmutableList
da Guava é praticamente inútil. Esse é um ótimo exemplo de código, muito confiável ao trabalhar com listas imutáveis locais (é por isso que eu o uso ativamente), mas, recorrendo a ele, é muito fácil ir além da cidadela compilada garantida e inexpugnável, cujas paredes eram compostas por tipos imutáveis - e somente nessa tipos imutáveis podem revelar completamente seu potencial. Isso é melhor que nada, mas ineficiente como uma solução JDK.
Se o
ImmutableList
não puder estender a
List
e a solução alternativa ainda não funcionar, como é que isso fará com que tudo funcione?
Imutabilidade é um recurso
O problema que encontramos nas duas primeiras tentativas de adicionar tipos imutáveis foi nosso equívoco de que a imutabilidade é simplesmente a ausência de algo: pegamos uma
List
, removemos o código alterado e obtemos uma lista
ImmutableList
. Mas, de fato, tudo isso não funciona dessa maneira.
Se simplesmente removermos os métodos de modificação da
List
, obteremos uma lista somente leitura. Ou, seguindo a terminologia formulada acima, ela pode ser chamada
UnmodifiableList
- ainda pode mudar, mas você não a mudará.
Agora podemos adicionar mais duas coisas a esta imagem:
- Podemos torná-lo mutável adicionando métodos apropriados
- Podemos torná-lo imutável adicionando garantias apropriadas.
Imutabilidade não é a ausência de mutabilidade, mas uma garantia de que não haverá mudanças
Nesse caso, é importante entender que, nos dois casos, estamos falando de recursos completos - imutabilidade não é a ausência de alterações, mas uma garantia de que não haverá alterações. Um recurso existente pode não ser necessariamente algo usado para o bem, mas também pode garantir que algo ruim não aconteça no código - nesse caso, pense, por exemplo, na segurança do encadeamento.
Obviamente, mutabilidade e imutabilidade conflitam entre si e, portanto, não podemos usar simultaneamente as duas hierarquias de herança acima mencionadas. Os tipos herdam recursos de outros tipos; portanto, não importa como você os recorte, se um deles herdar do outro, ele conterá os dois recursos.
Tão bem,
List
e
ImmutableList
não podem se estender. Mas fomos trazidos para cá trabalhando com
UnmodifiableList
e, na verdade, os dois tipos têm a mesma API, somente leitura, o que significa que eles devem estendê-la.

Embora eu não chamaria as coisas exatamente desses nomes, a hierarquia desse tipo é razoável. Em Scala, por exemplo, isso é
praticamente feito . A diferença é que o supertipo compartilhado, que chamamos de
UnmodifiableList
, define métodos mutáveis que retornam uma coleção modificada e deixam o original intocado. Assim, uma lista imutável acaba sendo
persistente e fornece a uma variante mutável dois conjuntos de métodos de modificação - herdados para receber cópias modificadas e próprias para alterações no local.
E o Java? É possível modernizar essa hierarquia adicionando novos supertipos e irmãos a ela?
É possível melhorar coleções não modificáveis e imutáveis?
Obviamente, não há problema em adicionar os
ImmutableList
e
ImmutableList
e criar a hierarquia de herança descrita acima. O problema é que, a curto e médio prazo, será praticamente inútil. Deixe-me explicar.
O
ImmutableList
ter
UnmodifiableList
,
ImmutableList
e
List
como tipos - nesse caso, as APIs poderão expressar claramente o que precisam e o que oferecem.
public void payAgents(UnmodifiableList<Agent> agents) {
No entanto, a menos que você inicie o projeto do zero, provavelmente terá essa funcionalidade e será algo parecido com isto:
Isso não é bom, porque, para que as novas coleções que acabamos de apresentar sejam úteis, precisamos trabalhar com elas! (ufa) O acima é remanescente do código do aplicativo, portanto, a refatoração
ImmutableList
UnmodifiableList
e
ImmutableList
, e você pode implementá-lo, como foi mostrado na lista acima. Isso pode ser uma grande parte do trabalho, juntamente com confusão quando você precisa organizar a interação do código antigo e atualizado, mas pelo menos parece viável.
E as estruturas, bibliotecas e o próprio JDK? Tudo aqui parece sombrio. Uma tentativa de alterar o parâmetro ou o tipo de retorno de
List
para
ImmutableList
levará à
incompatibilidade com o código-fonte , ou seja, o código fonte existente não será compilado com a nova versão, pois esses tipos não estão relacionados entre si. Da mesma forma, alterar o tipo de retorno de
List
para um novo supertipo
UnmodifiableList
resultará em erros de compilação.
Com a introdução de novos tipos, será necessário fazer alterações e recompilar todo o ecossistema.
No entanto, mesmo se expandirmos o tipo de parâmetro de
List
para
UnmodifiableList
, encontraremos um problema, pois essa alteração causa
incompatibilidade no nível do bytecode . Quando o código-fonte chama um método, o compilador converte essa chamada em bytecode que faz referência ao método de destino por:
- O nome da classe cuja instância o destino é declarado
- Nome do método
- Tipos de parâmetros de método
- Tipo de retorno de método
Qualquer alteração no parâmetro ou no tipo de retorno do método fará com que o bytecode indique a assinatura incorreta ao se referir ao método; como resultado, um erro
NoSuchMethodError
ocorrerá durante a execução. Se a alteração feita for compatível com o código-fonte - por exemplo, se você estiver falando sobre restringir o tipo de retorno ou expandir o tipo do parâmetro - a recompilação deve ser suficiente. No entanto, com mudanças de longo alcance, por exemplo, com a introdução de novas coleções, tudo não é tão simples: para consolidar essas alterações, é necessário recompilar todo o ecossistema Java. Este é um negócio perdido.
A única maneira concebível de tirar proveito dessas novas coleções sem quebrar a compatibilidade é duplicar cada método existente com um novo nome, alterar a API e marcar a versão antiga como indesejável. Você pode imaginar como essa tarefa seria monumental e virtualmente infinita ?!
Reflexão
É claro que tipos de coleções imutáveis são ótimas coisas que eu adoraria ter, mas é improvável que vejamos algo assim no JDK. As implementações competentes de
List
e
ImmutableList
nunca
ImmutableList
se estender (na verdade, elas estendem o mesmo tipo de lista
UnmodifiableList
, somente leitura), o que dificulta a implementação desses tipos nas APIs existentes.
Fora do contexto de qualquer relacionamento específico entre tipos, a alteração das assinaturas de métodos existentes sempre está repleta de problemas, pois essas alterações são incompatíveis com o bytecode. Quando elas são introduzidas, é necessária pelo menos a recompilação, e essa é uma mudança devastadora que afetará todo o ecossistema Java.
Portanto, acredito que nada disso vai acontecer - nunca e nunca.