Favoreca Imutabilidade e Simplicidade
Favoreca Imutabilidade e Simplicidade
Favoreca Imutabilidade e Simplicidade
String s = "java";
s.toUpperCase();
System.out.println(s);
Simplicidade e previsibilidade
O primeiro ponto é que objetos imutáveis são muito mais simples de se usar que
objetos mutáveis, pois acabam tendo um comportamento mais que previsível:
String s = "arquitetura";
metodoMisterioso(s);
System.out.println(s);
O que será mostrado na terceira linha, com absoluta certeza? Não importando o
que o metodoMisterioso faça, aparecerá arquitetura. Objetos imutáveis não sofrem
efeitos colaterais, pois têm comportamento previsível em relação ao seu
estado. Compare com um código semelhante mas passando uma referência a um
objeto mutável, no caso um Calendar:
Calendar c = Calendar.getInstance();
metodoMisterioso(c);
System.out.println(c.get(Calendar.YEAR));
Qual ano será impresso na terceira linha acima? O ano atual? Não é possível saber
só com essas informações, pois o metodoMisterioso pode ter alterado o ano do nosso
objeto Calendar.
Os próprios engenheiros da Sun já admitiram alguns erros de design das APIs
antigas, como a classe java.util.Calendar ser mutável [16]. Por isso, muitas vezes
recorremos às APIs de terceiros como a Joda Time, onde encontramos entidades de
datas, horários e intervalos imutáveis.
Quando o objeto é mutável, para evitar quem alguém o modifique, precisamos sem-
pre interfaceá-lo ou embrulhá-lo de tal forma que os métodos expostos não possibilitem
modificação. É como, por exemplo, fazemos frequentemente com coleções através do
Collections.unmodifiableList(List) e seus similares, criando uma cópia defensiva:
i i
i i
i i
em vez de passar a referência original adiante, com medo de ocorrer uma modificação
por parte do código de outra pessoal.
Objetos imutáveis são mais simples de se lidar. Depois de sua criação, sempre
saberemos seu valor e não precisamos tomar cuidados adicionais para preservá-lo. Já
objetos mutáveis podem ser mudados e, frequentemente, temos que passar cópias dos
mesmos com o objetivo de evitar alterações indevidas.
Otimizações de memória
Podemos tirar proveito da imutabilidade de objetos de outras formas [22]. Como
cada objeto representa apenas um estado, você não precisa mais do que uma instância
de cada estado. A própria API da linguagem Java usa a ideia de imutabilidade como
uma vantagem para fazer cache e reaproveitamento de objetos.
Existe, dentro da VM, um pool de Strings que faz com que Strings com o mesmo
conteúdo na verdade possam ser representadas com um mesmo objeto compartilhado
(no caso, a array privada de caracteres pode ser compartilhada). O mesmo acontece
em boa parte das implementações das classes wrapper como Integer. Ao invocar
Integer.valueOf(int), o objeto Integer devolvido pode ser fruto de um cache interno
de objetos com números frequentemente solicitados [39].
Esse tipo de otimização só é possível com objetos imutáveis. É seguro compartilhar
instâncias de Strings ou Integers com várias partes do programa, sem o medo de que
alguém possa alterar o objeto e afetar os outros.
E há mais possibilidades ainda para otimizações graças à imutabilidade. A classe
String, por exemplo, ainda se aproveita de sua imutabilidade para compartilhar seu
array de char interno entre Strings diferentes. Se olharmos o código fonte da classe
String, veremos que ela possui três atributos principais: um char[] value e dois in-
teiros, count e offset [36]. Os dois inteiros servem para controlar o início e o fim da
String dentro do array de char.
O seu método substring(int,int) leva em conta a imutabilidade das Strings e os
dois inteiros que controlam início e fim para reaproveitar o array de char. Quando
pedimos uma determinada substring, ao invés do Java criar um novo array de char
com o pedaço em questão, ele devolve um novo objeto String que internamente possui
uma referência para o mesmo array de char que a String original, e tem apenas os seus
dois índices inteiros ajustados. Ou seja, criar uma substring em Java praticamente não
gasta mais memória, sendo apenas uma questão de ajustar dois números inteiros
Essa otimização é uma implementação um pouco mais simples do design pattern
Flyweight, onde propõe-se reaproveitar o estado interno entre objetos imutáveis com
objetivo de reaproveitar o uso memória [?]. Poderíamos até ir mais longe com o pattern
flyweight, por exemplo construindo Strings a partir de mais de uma String já usada
em memória (nesse caso a API do Java prefere não chegar a esse ponto, pois apesar de
um ganho de memória, haveria o tradeoff de perda de performance).
É válido ressaltar também o perigo dessa estratégia de otimização. Uma String
pequena pode acabar segurando referência para uma array de chars muito grande se
ela foi originada a partir de uma String grande, impedindo que essa array maior seja
coletada mesmo se não possuirmos referências para a String original. Outro problema
comum de aparecer com objetos imutáveis é o excesso de memória consumido tempora-
i i
i i
i i
riamente. Cada invocação de método pode criar uma nova cópia do objeto apenas com
alguma alteração e, se isto estiver dentro de um laço, diversas cópias temporárias serão
utilizadas, mas apenas o resultado final é guardado. Aqui, o uso do design pattern
builder pode ajudar, como é o caso de usarmos a classe mutável StringBuilder (e sua
versão thread safe StringBuffer) e seu método append em vez do String.concat (ou o
operador sobrecarregado +) em um laço.
Objetos imutáveis trazem uma segurança tão grande em relação a compartilhar
instâncias sem se preocupar com alterações indevidas que podemos usar esse fato a
nosso favor quando for importante economizar memória.
i i
i i
i i
O mesmo ocorre com algumas outras classes imutáveis muito conhecidas do Java,
como BigDecimal e BigInteger. Vale lembrar que, para aplicar fluent interface, não
necessariamente o objeto precisa ser imutável, como é o caso das classes StringBuffer
e StringBuilder.
i i
i i
i i
[11]:
• A classe deve ser final para evitar que filhas permitam mutabilidade;
• Os atributos devem ser final, apenas para legibilidade de código, já que não há
métodos que modificam o estado do objeto;
• Caso sua classe tenha composições com objetos mutáveis, eles devem ter acesso
exclusivo pela sua classe (devolvendo cópias defensivas quando necessário).
O código começa a ficar mais complicado quando nossa classe imutável depende
de objetos mutáveis. Por exemplo, uma classe Periodo imutável que, em Java, seja
implementada através de Calendars.
É preciso trabalhar com cópias defensivas em dois momentos. Primeiro quando
recebemos algum Calendar como parâmetro e vamos armazená-lo internamente. E
depois quando formos devolver algum Calendar que faça parte do estado do objeto.
Se não fizermos as cópias, é possível quealgum código externo a nossa classe Periodo
altere os Calendars em questão.
Em código: (note as cópias defensivas no construtor e nos getters)
i i
i i
i i
i i
i i