Por que precisamos de class imutável?

Eu não consigo entender quais são os cenários em que precisamos de uma class imutável.
Você já enfrentou algum desses requisitos? ou você pode, por favor, nos dar um exemplo real de onde devemos usar esse padrão?

As outras respostas parecem focadas em explicar por que a imutabilidade é boa. É muito bom e eu uso sempre que possível. No entanto, essa não é a sua pergunta . Levarei sua questão ponto a ponto para tentar garantir que você receba as respostas e os exemplos de que precisa.

Eu não consigo entender quais são os cenários em que precisamos de uma class imutável.

“Need” é um termo relativo aqui. As classs imutáveis ​​são um padrão de design que, como qualquer paradigma / padrão / ferramenta, está lá para facilitar a construção de software. Da mesma forma, muito código foi escrito antes do paradigma OO aparecer, mas conte-me entre os programadores que “precisam” de OO. Aulas imutáveis, como OO, não são estritamente necessárias , mas eu vou agir como eu preciso delas.

Você já enfrentou algum desses requisitos?

Se você não estiver olhando para os objects no domínio do problema com a perspectiva correta, talvez não veja um requisito para um object imutável. Pode ser fácil pensar que um domínio problemático não requer classs imutáveis ​​se você não estiver familiarizado quando usá-las vantajosamente.

Costumo usar classs imutáveis ​​em que penso em um determinado object no domínio do meu problema como um valor ou instância fixa . Essa noção às vezes depende da perspectiva ou do ponto de vista, mas, idealmente, será fácil alternar para a perspectiva correta para identificar bons objects candidatos.

Você pode ter uma noção melhor de onde objects imutáveis ​​são realmente úteis (se não for estritamente necessário) certificando-se de ler vários livros / artigos on-line para desenvolver um bom senso de como pensar em classs imutáveis. Um bom artigo para você começar é a teoria e a prática de Java: para mudar ou não para mutação?

Vou tentar dar alguns exemplos abaixo de como se pode ver objects em diferentes outlook (mutável vs imutável) para esclarecer o que quero dizer com perspectiva.

… você pode nos dar um exemplo real de onde devemos usar esse padrão?

Desde que você pediu exemplos reais eu vou te dar alguns, mas primeiro vamos começar com alguns exemplos clássicos.

Objetos Clássicos de Valor

Strings e inteiros e são frequentemente considerados como valores. Portanto, não é surpreendente descobrir que a class String e a class wrapper Integer (assim como as outras classs wrapper) são imutáveis ​​em Java. Uma cor geralmente é considerada como um valor, portanto, a class de colors imutável.

Contra-exemplo

Em contraste, um carro geralmente não é considerado um object de valor. Modelar um carro geralmente significa criar uma class que tenha estado em mudança (odômetro, velocidade, nível de combustível, etc). No entanto, existem alguns domínios em que o carro pode ser um object de valor. Por exemplo, um carro (ou especificamente um modelo de carro) pode ser considerado como um object de valor em um aplicativo para procurar o óleo de motor adequado para um determinado veículo.

Jogando cartas

Já escreveu um programa de cartas de jogar? Eu fiz. Eu poderia ter representado uma carta de baralho como um object mutável com um naipe e rank mutável. Uma mão draw-poker poderia ser 5 instâncias fixas em que a substituição da 5ª carta na minha mão significaria a mutação da 5ª instância da carta de baralho para uma nova carta, alterando seu naipe e rank ivars.

No entanto, eu costumo pensar em uma carta de baralho como um object imutável que tem um naipe fixo imutável e rank uma vez criado. Minha mão de draw poker seria 5 instâncias e replace uma carta em minha mão envolveria descartar uma dessas instâncias e adicionar uma nova instância aleatória à minha mão.

Projeção de Mapa

Um último exemplo é quando eu trabalhei em algum código de mapa onde o mapa poderia se mostrar em várias projeções . O código original tinha o mapa usando uma instância de projeção fixa, mas mutável (como a carta de jogo mutável acima). Alterar a projeção do mapa significava a mutação dos ivars da instância de projeção do mapa (tipo de projeção, ponto central, zoom, etc).

No entanto, senti que o design era mais simples se eu pensasse em uma projeção como um valor imutável ou uma instância fixa. Alterar a projeção do mapa significava que o mapa referenciava uma instância de projeção diferente, em vez de alterar a instância de projeção fixa do mapa. Isso também tornou mais simples capturar projeções nomeadas como MERCATOR_WORLD_VIEW .

As classs imutáveis ​​são, em geral, muito mais simples de projetar, implementar e usar corretamente . Um exemplo é String: a implementação de java.lang.String é significativamente mais simples que std::string em C ++, principalmente devido à sua imutabilidade.

Uma área em particular onde a imutabilidade faz uma diferença especialmente grande é a simultaneidade: objects imutáveis ​​podem ser compartilhados com segurança entre vários encadeamentos , enquanto objects mutáveis ​​devem ser protegidos contra encadeamentos por meio de planejamento e implementação cuidadosos – geralmente isso está longe de ser uma tarefa trivial.

Atualização: O Effective Java 2nd Edition aborda esse problema detalhadamente – consulte o Item 15: Minimizar mutabilidade .

Veja também estes posts relacionados:

  • benefícios não-técnicos de ter imutáveis ​​do tipo string
  • Desvantagens para objects imutáveis ​​em Java?

Java eficaz por Joshua Bloch descreve várias razões para escrever classs imutáveis:

  • Simplicidade – cada class está em apenas um estado
  • Segmento Segura – porque o estado não pode ser alterado, nenhuma synchronization é necessária
  • Escrever em um estilo imutável pode levar a um código mais robusto. Imagine se as cordas não fossem imutáveis; Quaisquer methods getter que retornassem uma String exigiriam que a implementação criasse uma cópia defensiva antes que a String fosse retornada – caso contrário, um cliente pode quebrar acidentalmente ou mal-intencionado esse estado do object.

Em geral, é uma boa prática tornar um object imutável, a menos que haja problemas graves de desempenho como resultado. Em tais circunstâncias, objects construtivos mutáveis ​​podem ser usados ​​para construir objects imutáveis, por exemplo, StringBuilder

Hashmaps são um exemplo clássico. É imperativo que a chave para um mapa seja imutável. Se a chave não for imutável e você alterar um valor na chave, de modo que hashCode () resultaria em um novo valor, o mapa agora está quebrado (uma chave agora está no local errado na tabela de hash.).

Java é praticamente uma e todas as referências. Às vezes, uma instância é referenciada várias vezes. Se você alterar essa instância, ela será refletida em todas as suas referências. Às vezes você simplesmente não quer ter isso para melhorar a robustez e a segurança do fio. Então, uma class imutável é útil para que uma pessoa seja forçada a criar uma nova instância ea reatribuir à referência atual. Desta forma, a instância original das outras referências permanece intocada.

Imagine como seria o Java se o String fosse mutável.

Nós não precisamos de classs imutáveis, mas elas podem certamente tornar algumas tarefas de programação mais fáceis, especialmente quando múltiplos encadeamentos estão envolvidos. Você não precisa executar nenhum bloqueio para acessar um object imutável, e quaisquer fatos que você já tenha estabelecido sobre esse object continuarão a ser verdadeiros no futuro.

Existem vários motivos para a imutabilidade:

  • Segurança de Encadeamento: Objetos imutáveis ​​não podem ser alterados nem seu estado interno pode mudar, portanto, não há necessidade de sincronizá-lo.
  • Também garante que tudo o que eu enviar através (através de uma rede) tem que vir no mesmo estado que anteriormente enviado. Isso significa que ninguém (bisbilhoteiro) pode vir e adicionar dados randoms no meu conjunto imutável.
  • Também é mais simples de desenvolver. Você garante que nenhuma subclass existirá se um object for imutável. Por exemplo, uma class String .

Portanto, se você quiser enviar dados por meio de um serviço de rede e quiser ter uma garantia de que terá seu resultado exatamente igual ao que enviou, defina-o como imutável.

Vamos pegar um caso extremo: constantes inteiras. Se eu escrever uma declaração como “x = x + 1” eu quero ser 100% confiante de que o número “1” não se tornará de alguma forma 2, não importa o que aconteça em qualquer outro lugar do programa.

Agora tudo bem, as constantes inteiras não são uma class, mas o conceito é o mesmo. Suponha que eu escreva:

 String customerId=getCustomerId(); String customerName=getCustomerName(customerId); String customerBalance=getCustomerBalance(customerid); 

Parece bastante simples. Mas, se as Strings não fossem imutáveis, eu teria que considerar a possibilidade de que getCustomerName alterasse customerId, de modo que, quando eu chame getCustomerBalance, estou obtendo o saldo de um cliente diferente. Agora você pode dizer: “Por que alguém no mundo escreveria uma function getCustomerName para alterar o id? Isso não faria sentido”. Mas é exatamente aí que você pode ter problemas. A pessoa que escreve o código acima pode considerar óbvio que as funções não mudariam o parâmetro. Então surge alguém que precisa modificar outro uso dessa function para lidar com o caso em que um cliente tem várias contas com o mesmo nome. E ele diz: “Ah, aqui está a function de nome getCustomer acessível que já está procurando o nome. Eu só vou fazer isso automaticamente mudar o id para a próxima conta com o mesmo nome e colocá-lo em um loop …” E então seu programa começa misteriosamente não funcionando. Isso seria um estilo ruim de codificação? Provavelmente. Mas é precisamente um problema nos casos em que o efeito colateral NÃO é óbvio.

Imutabilidade significa simplesmente que uma certa class de objects são constantes, e podemos tratá-los como constantes.

(Claro que o usuário poderia atribuir um “object constante” diferente a uma variável. Alguém pode escrever String s = “olá” e depois escrever s = “adeus”; a menos que eu torne a variável final, não posso ter certeza que não está sendo alterado dentro do meu próprio bloco de código, assim como constantes inteiras me asseguram que “1” é sempre o mesmo número, mas não que “x = 1” nunca será alterado escrevendo “x = 2”. posso estar confiante de que se eu tiver um identificador para um object imutável, que nenhuma function que eu passe para ele pode mudar isso em mim, ou que se eu fizer duas cópias dele, que uma mudança na variável contendo uma cópia não mudará outro, etc.

Eu vou atacar isso de uma perspectiva diferente. Eu acho objects imutáveis ​​facilitam a vida para mim quando leio código.

Se eu tiver um object mutável, nunca tenho certeza de qual é o seu valor se ele for usado fora do meu escopo imediato. Digamos que eu crie MyMutableObject em variables ​​locais de um método, preencha-o com valores e passe-o para cinco outros methods. Qualquer um desses methods pode alterar o estado do meu object, portanto, uma das duas coisas deve ocorrer:

  1. Eu tenho que acompanhar os corpos de cinco methods adicionais enquanto penso na lógica do meu código.
  2. Eu tenho que fazer cinco cópias defensivas do meu object para garantir que os valores corretos sejam passados ​​para cada método.

O primeiro dificulta o raciocínio sobre o meu código. O segundo faz meu código sugar o desempenho – estou basicamente imitando um object imutável com uma semântica de copy-on-write, mas fazendo isso o tempo todo, independentemente de os methods chamados realmente modificarem o estado do meu object.

Se eu, em vez disso, usar MyImmutableObject , posso ter certeza de que o que eu defino é o que os valores serão para a vida do meu método. Não há uma “ação fantasmagórica a distância” que irá mudar de mim e não há necessidade de fazer cópias defensivas do meu object antes de invocar os outros cinco methods. Se os outros methods querem mudar as coisas para seus propósitos, eles precisam fazer a cópia – mas eles só fazem isso se realmente precisarem fazer uma cópia (em vez de fazer isso antes de cada chamada de método externo). Eu me poupo dos resources mentais de rastrear methods que podem nem estar em meu arquivo fonte atual, e poupo o sistema da sobrecarga de fazer infinitamente cópias defensivas desnecessárias apenas no caso.

(Se eu for para fora do mundo Java e, digamos, para o mundo C ++, entre outros, posso ficar ainda mais complicado. Posso fazer com que os objects pareçam mutáveis, mas nos bastidores faça-os transparentemente clonar qualquer tipo de mudança de estado – isso é copy-on-write – com ninguém sendo o mais sábio.)

Usar a palavra final não necessariamente torna algo imutável:

 public class Scratchpad { public static void main(String[] args) throws Exception { SomeData sd = new SomeData("foo"); System.out.println(sd.data); //prints "foo" voodoo(sd, "data", "bar"); System.out.println(sd.data); //prints "bar" } private static void voodoo(Object obj, String fieldName, Object value) throws Exception { Field f = SomeData.class.getDeclaredField("data"); f.setAccessible(true); Field modifiers = Field.class.getDeclaredField("modifiers"); modifiers.setAccessible(true); modifiers.setInt(f, f.getModifiers() & ~Modifier.FINAL); f.set(obj, "bar"); } } class SomeData { final String data; SomeData(String data) { this.data = data; } } 

Apenas um exemplo para demonstrar que a palavra-chave “final” está lá para evitar erros no programador e não muito mais. Enquanto a reatribuição de um valor sem uma palavra-chave final pode ocorrer facilmente por acidente, ir até este limite para alterar um valor teria que ser feito intencionalmente. Está lá para documentação e para evitar erros no programador.

Estruturas de dados imutáveis ​​também podem ajudar na codificação de algoritmos recursivos. Por exemplo, digamos que você esteja tentando resolver um problema do 3SAT . Uma maneira é fazer o seguinte:

  • Escolha uma variável não atribuída.
  • Dê o valor de TRUE. Simplifique a instância removendo as cláusulas que agora estão satisfeitas e recorrendo para resolver a instância mais simples.
  • Se a recursion no caso TRUE falhar, atribua a variável FALSE ao invés. Simplifique essa nova instância e recorra para resolvê-la.

Se você tiver uma estrutura mutável para representar o problema, quando você simplificar a instância na ramificação TRUE, será necessário:

  • Acompanhe todas as alterações feitas e desfaça todas elas depois que você perceber que o problema não pode ser solucionado. Isso tem uma grande sobrecarga porque sua recursion pode ser bem profunda e é complicado codificar.
  • Faça uma cópia da instância e modifique a cópia. Isso será lento porque se sua recursion tiver alguns níveis de profundidade, você terá que fazer muitas cópias da instância.

No entanto, se você codificá-lo de uma maneira inteligente, você pode ter uma estrutura imutável, onde qualquer operação retorna uma versão atualizada (mas ainda imutável) do problema (semelhante a String.replace – ele não substitui a string, apenas fornece um novo). A maneira ingênua de implementar isso é fazer com que a estrutura “imutável” simplesmente copie e faça uma nova em qualquer modificação, reduzindo-a à segunda solução ao ter uma mutável, com toda essa sobrecarga, mas você pode fazer isso de uma forma mais jeito eficiente.

Uma das razões para a “necessidade” de classs imutáveis ​​é a combinação de passar tudo por referência e não ter suporte para visualizações somente leitura de um object (isto é, o const C ++).

Considere o caso simples de uma class com suporte para o padrão de observador:

 class Person { public string getName() { ... } public void registerForNameChange(NameChangedObserver o) { ... } } 

Se string não fosse imutável, seria impossível para a class Person implementar registerForNameChange() corretamente, porque alguém poderia escrever o seguinte, modificando efetivamente o nome da pessoa sem acionar qualquer notificação.

 void foo(Person p) { p.getName().prepend("Mr. "); } 

Em C ++, getName() retornando um const std::string& tem o efeito de retornar por referência e impedindo o access a mutators, o que significa que classs imutáveis ​​não são necessárias nesse contexto.

Eles também nos dão uma garantia. A garantia de imutabilidade significa que podemos expandi-las e criar novos padrões de eficiência que de outra forma não seriam possíveis.

http://en.wikipedia.org/wiki/Singleton_pattern

Uma característica de classs imutáveis ​​que ainda não foi chamada: armazenar uma referência a um object de class profundamente imutável é um meio eficiente de armazenar todo o estado contido nele. Suponha que eu tenha um object mutável que use um object profundamente imutável para armazenar 50K de informações de estado. Suponha, além disso, que eu deseje, em 25 ocasiões, fazer uma “cópia” do meu object original (mutável) (por exemplo, para um buffer “desfazer”); o estado pode mudar entre as operações de cópia, mas geralmente não muda. Fazer uma “cópia” do object mutável exigiria simplesmente a cópia de uma referência ao seu estado imutável, de modo que 20 cópias seriam simplesmente 20 referências. Por outro lado, se o estado fosse mantido em valor de 50 K de objects mutáveis, cada uma das 25 operações de cópia teria que produzir sua própria cópia de 50 K de dados; A realização de todas as 25 cópias exigiria uma quantidade maior de dados duplicados. Mesmo que a primeira operação de cópia produza uma cópia dos dados que nunca serão alterados, e as outras 24 operações poderiam, em teoria, simplesmente referir-se a isso, na maioria das implementações não haveria maneira de o segundo object solicitar uma cópia do arquivo. informação para saber que uma cópia imutável já existe (*).

(*) Um padrão que às vezes pode ser útil é que objects mutáveis ​​tenham dois campos para manter seu estado – um em forma mutável e outro em formato imutável. Os objects podem ser copiados como mutáveis ​​ou imutáveis ​​e começam a vida com um ou outro conjunto de referência. Tão logo o object queira mudar seu estado, ele copia a referência imutável para a mutável (se já não foi feita) e invalida a imutável. Quando o object é copiado como imutável, se sua referência imutável não for definida, uma cópia imutável será criada e a referência imutável apontada para isso. Essa abordagem exigirá mais algumas operações de cópia do que uma “cópia completa na gravação” (por exemplo, pedir para copiar um object que tenha sofrido mutação desde a última cópia exigiria uma operação de cópia, mesmo que o object original nunca seja novamente transformado ) mas evita as complexidades de encadeamento que o FFCOW implicaria.

Objetos imutáveis ​​são instâncias cujos estados não mudam depois de iniciados. O uso de tais objects é específico do requisito.

Classe imutável é boa para fins de cache e é thread-safe.

de Java efetivo; Uma class imutável é simplesmente uma class cujas instâncias não podem ser modificadas. Todas as informações contidas em cada instância são fornecidas quando são criadas e são fixadas para o tempo de vida do object. As bibliotecas da plataforma Java contêm muitas classs imutáveis, incluindo String, as classs primitivas em checkbox e BigInteger e BigDecimal. Existem muitas boas razões para isso: classs imutáveis ​​são mais fáceis de projetar, implementar e usar do que classs mutáveis. Eles são menos propensos a erros e são mais seguros.

Por imutabilidade, você pode ter certeza de que o comportamento não muda, com isso, você obtém a vantagem de executar operações adicionais:

  • Você pode usar vários núcleos / processamentos (processamento simultâneo ) com facilidade (já que a sequência não terá mais importância).

  • Pode fazer cache para operação cara (como você tem certeza do mesmo
    resultado).

  • Pode depurar com facilidade (como a história da execução não será preocupante
    não mais)

Por que class imutável?

Quando um object é instanciado, o estado não pode ser alterado no tempo de vida. Que também faz com que seja thread seguro.

Exemplos :

Obviamente, String, Integer e BigDecimal, etc. Uma vez que estes valores são criados, não podem ser alterados no tempo de vida.

Caso de uso: Uma vez criado o object de conexão com o database com seus valores de configuração, talvez não seja necessário alterar seu estado, no qual você pode usar uma class imutável.

Meus 2 centavos para futuros visitantes:

2 cenários onde objects imutáveis ​​são boas escolhas são:

Em multi-threading

Problemas de concorrência no ambiente multissegmentado podem muito bem ser resolvidos por synchronization, mas a synchronization é dispendiosa (não seria aqui “por que”), então se você estiver usando objects imutáveis, não haverá synchronization para resolver o problema de concorrência. objects imutáveis ​​não podem ser alterados e, se o estado não puder ser alterado, todos os encadeamentos poderão acessar o object com perfeição. Assim, objects imutáveis ​​são uma ótima opção para objects compartilhados no ambiente multiencadeado.

Como chave para collections baseadas em hash

Uma das coisas mais importantes a se notar quando se trabalha com coleta baseada em hash é que a chave deve ser tal que seu hashCode() deva sempre retornar o mesmo valor para a vida útil do object, porque se esse valor for alterado, A coleta baseada em hash usando esse object não pode ser recuperada, portanto, causaria memory leaks. Como o estado dos objects imutáveis ​​não pode ser alterado, eles são uma ótima escolha como chave na coleta baseada em hash. Portanto, se você estiver usando um object imutável como chave para a coleta baseada em hash, pode ter certeza de que não haverá memory leaks por causa disso (é claro que ainda pode haver memory leaks quando o object usado como chave não é referenciado em nenhum lugar mais, mas esse não é o ponto aqui).