Como tornar uma classe Java imutável, qual é a necessidade de imutabilidade e há alguma vantagem em usar isso?
Como tornar uma classe Java imutável, qual é a necessidade de imutabilidade e há alguma vantagem em usar isso?
Respostas:
O que é um objeto imutável?
Um objeto imutável é aquele que não muda de estado após ser instanciado.
Como tornar um objeto imutável?
Em geral, um objeto imutável pode ser feito definindo uma classe que não tem nenhum de seus membros expostos e não tem nenhum setter.
A seguinte classe criará um objeto imutável:
class ImmutableInt {
private final int value;
public ImmutableInt(int i) {
value = i;
}
public int getValue() {
return value;
}
}
Como pode ser visto no exemplo acima, o valor de ImmutableInt
só pode ser definido quando o objeto é instanciado e, por ter apenas um getter ( getValue
), o estado do objeto não pode ser alterado após a instanciação.
No entanto, deve-se ter cuidado para que todos os objetos que são referenciados pelo objeto também sejam imutáveis, ou pode ser possível alterar o estado do objeto.
Por exemplo, permitir uma referência a uma matriz ou ArrayList
ser obtida por meio de um getter permitirá que o estado interno mude, alterando a matriz ou coleção:
class NotQuiteImmutableList<T> {
private final List<T> list;
public NotQuiteImmutableList(List<T> list) {
// creates a new ArrayList and keeps a reference to it.
this.list = new ArrayList(list);
}
public List<T> getList() {
return list;
}
}
O problema com o código acima é que o ArrayList
pode ser obtido getList
e manipulado, fazendo com que o estado do próprio objeto seja alterado, portanto, não imutável.
// notQuiteImmutableList contains "a", "b", "c"
List<String> notQuiteImmutableList= new NotQuiteImmutableList(Arrays.asList("a", "b", "c"));
// now the list contains "a", "b", "c", "d" -- this list is mutable.
notQuiteImmutableList.getList().add("d");
Uma maneira de contornar esse problema é retornar uma cópia de uma matriz ou coleção quando chamada de um getter:
public List<T> getList() {
// return a copy of the list so the internal state cannot be altered
return new ArrayList(list);
}
Qual é a vantagem da imutabilidade?
A vantagem da imutabilidade vem com a simultaneidade. É difícil manter a correção em objetos mutáveis, pois vários threads podem estar tentando alterar o estado do mesmo objeto, levando a alguns threads vendo um estado diferente do mesmo objeto, dependendo do tempo de leitura e gravação no referido objeto.
Por ter um objeto imutável, pode-se garantir que todos os threads que estão olhando para o objeto verão o mesmo estado, pois o estado de um objeto imutável não mudará.
return Collections.unmodifiableList(list);
retornar uma exibição somente leitura na lista.
final
também. Caso contrário, ele pode ser estendido com um método setter (ou outros tipos de métodos mutantes e campos mutáveis).
final
se a classe tiver apenas private
campos, pois eles não podem ser acessados a partir de subclasses.
Além das respostas já fornecidas, eu recomendo ler sobre imutabilidade em Effective Java, 2ª Ed., Pois existem alguns detalhes que são fáceis de ignorar (por exemplo, cópias defensivas). Além disso, Effective Java 2ª Ed. é uma leitura obrigatória para todo desenvolvedor Java.
Você torna uma classe imutável assim:
public final class Immutable
{
private final String name;
public Immutable(String name)
{
this.name = name;
}
public String getName() { return this.name; }
// No setter;
}
A seguir estão os requisitos para tornar uma classe Java imutável:
final
(para que as classes filhas não possam ser criadas)final
(para que não possamos alterar seu valor após a criação do objeto)Classes imutáveis são úteis porque
- Elas são seguras com thread.
- Eles também expressam algo profundo sobre o seu design: "Não é possível mudar isso.", Quando se aplica, é exatamente o que você precisa.
A imutabilidade pode ser alcançada principalmente de duas maneiras:
final
atributos de instância para evitar reatribuiçãoAs vantagens da imutabilidade são as suposições que você pode fazer sobre estes objetos:
Classes imutáveis não podem reatribuir valores após serem instanciadas. O construtor atribui valores a suas variáveis privadas. Até que o objeto se torne nulo, os valores não podem ser alterados devido à indisponibilidade dos métodos setter.
ser imutável deve satisfazer o seguinte,
/**
* Strong immutability - by making class final
*/
public final class TestImmutablity {
// make the variables private
private String Name;
//assign value when the object created
public TestImmutablity(String name) {
this.Name = name;
}
//provide getters to access values
public String getName() {
return this.Name;
}
}
Vantagens: Objetos imutáveis contêm seus valores inicializados até que morram.
Classe imutável são aquelas cujos objetos não podem ser alterados após a criação.
Classes imutáveis são úteis para
Exemplo
Classe String
Exemplo de Código
public final class Student {
private final String name;
private final String rollNumber;
public Student(String name, String rollNumber) {
this.name = name;
this.rollNumber = rollNumber;
}
public String getName() {
return this.name;
}
public String getRollNumber() {
return this.rollNumber;
}
}
Como tornar uma classe Java imutável?
Do JDK 14+ que tem JEP 359 , podemos usar "records
". É a maneira mais simples e descomplicada de criar uma classe Imutável.
Uma classe de registro é um portador transparente e superficialmente imutável para um conjunto fixo de campos conhecido como registro, components
que fornece uma state
descrição para o registro. Cada um component
dá origem a um final
campo que contém o valor fornecido e um accessor
método para recuperar o valor. O nome do campo e o nome do acessador correspondem ao nome do componente.
Vamos considerar o exemplo de criação de um retângulo imutável
record Rectangle(double length, double width) {}
Não há necessidade de declarar nenhum construtor, nem de implementar métodos equals & hashCode. Qualquer registro precisa de um nome e uma descrição de estado.
var rectangle = new Rectangle(7.1, 8.9);
System.out.print(rectangle.length()); // prints 7.1
Se você quiser validar o valor durante a criação do objeto, temos que declarar explicitamente o construtor.
public Rectangle {
if (length <= 0.0) {
throw new IllegalArgumentException();
}
}
O corpo do registro pode declarar métodos estáticos, campos estáticos, inicializadores estáticos, construtores, métodos de instância e tipos aninhados.
Métodos de Instância
record Rectangle(double length, double width) {
public double area() {
return this.length * this.width;
}
}
campos estáticos, métodos
Como o estado deve fazer parte dos componentes, não podemos adicionar campos de instância aos registros. Mas, podemos adicionar métodos e campos estáticos:
record Rectangle(double length, double width) {
static double aStaticField;
static void aStaticMethod() {
System.out.println("Hello Static");
}
}
qual é a necessidade de imutabilidade e há alguma vantagem em usar isso?
Respostas postadas anteriormente são boas o suficiente para justificar a necessidade de imutabilidade e seus prós
Outra maneira de tornar o objeto imutável é usando a biblioteca Immutables.org :
Supondo que as dependências necessárias foram adicionadas, crie uma classe abstrata com métodos de acesso abstratos. Você pode fazer o mesmo anotando com interfaces ou mesmo anotações (@interface):
package info.sample;
import java.util.List;
import java.util.Set;
import org.immutables.value.Value;
@Value.Immutable
public abstract class FoobarValue {
public abstract int foo();
public abstract String bar();
public abstract List<Integer> buz();
public abstract Set<Long> crux();
}
Agora é possível gerar e usar a implementação imutável gerada:
package info.sample;
import java.util.List;
public class FoobarValueMain {
public static void main(String... args) {
FoobarValue value = ImmutableFoobarValue.builder()
.foo(2)
.bar("Bar")
.addBuz(1, 3, 4)
.build(); // FoobarValue{foo=2, bar=Bar, buz=[1, 3, 4], crux={}}
int foo = value.foo(); // 2
List<Integer> buz = value.buz(); // ImmutableList.of(1, 3, 4)
}
}
Uma classe imutável é simplesmente uma classe cujas instâncias não podem ser modificadas.
Todas as informações contidas em cada instância são fixas para o tempo de vida do objeto, portanto, nenhuma mudança pode ser observada.
Classes imutáveis são mais fáceis de projetar, implementar e usar do que classes mutáveis.
Para tornar uma classe imutável, siga estas cinco regras:
Não forneça métodos que modifiquem o estado do objeto
Certifique-se de que a aula não pode ser estendida.
Faça todos os campos finais.
Torne todos os campos privados.
Garanta acesso exclusivo a quaisquer componentes mutáveis.
Objetos imutáveis são inerentemente thread-safe; eles não requerem sincronização.
Objetos imutáveis podem ser compartilhados livremente.
Objetos imutáveis são ótimos blocos de construção para outros objetos
@Jack, Ter campos finais e setters em classe não tornará a classe imutável. a palavra-chave final apenas garante que uma variável nunca seja reatribuída. Você precisa retornar uma cópia profunda de todos os campos nos métodos getter. Isso garantirá que, após obter um objeto do método getter, o estado interno do objeto não seja perturbado.
Como um falante não nativo de inglês, não gosto da interpretação comum de "classe imutável" sendo "objetos de classe construídos são imutáveis"; antes, eu mesmo me inclino a interpretar isso como "o próprio objeto de classe é imutável".
Dito isso, "classe imutável" é um tipo de objeto imutável. A diferença está em responder qual é o benefício. Pelo meu conhecimento / interpretação, a classe imutável impede que seus objetos modifiquem o comportamento em tempo de execução.
A maioria das respostas aqui são boas, e algumas mencionaram as regras, mas acho bom colocar em palavras por que e quando precisamos seguir essas regras. Então estou dando a explicação abaixo
E, claro, se tentarmos usar Setters para essas variáveis finais, o compilador gerará erro.
public class ImmutableClassExplored {
public final int a;
public final int b;
/* OR
Generally we declare all properties as private, but declaring them as public
will not cause any issues in our scenario if we make them final
public final int a = 109;
public final int b = 189;
*/
ImmutableClassExplored(){
this. a = 111;
this.b = 222;
}
ImmutableClassExplored(int a, int b){
this.a = a;
this.b= b;
}
}
Precisamos declarar a classe como 'final'?
1. Tendo apenas membros primitivos: Não temos problemas Se a classe tiver apenas membros primitivos, então não precisamos declarar a classe como final.
2. Tendo objetos como variáveis de membro: Se tivermos objetos como variáveis de membro, então temos que tornar os membros desses objetos também finais. Significa que precisamos atravessar profundamente a árvore e tornar todos os objetos / primitivos finais, o que pode não ser possível o tempo todo. Portanto, a solução alternativa é tornar a classe final, o que evita a herança. Portanto, não há dúvida de que métodos getter sobrescrevem subclasses.
A anotação @Value do Lombok pode ser usada para gerar classes imutáveis. É tão simples quanto o código abaixo.
@Value
public class LombokImmutable {
int id;
String name;
}
De acordo com a documentação no site do Lombok:
@Value é a variante imutável de @Data; todos os campos se tornam privados e finais por padrão, e os setters não são gerados. A própria classe também é finalizada por padrão, porque a imutabilidade não é algo que pode ser forçado em uma subclasse. Como @Data, os métodos úteis toString (), equals () e hashCode () também são gerados, cada campo obtém um método getter e um construtor que cobre todos os argumentos (exceto campos finais que são inicializados na declaração de campo) também é gerado .
Um exemplo totalmente funcional pode ser encontrado aqui.
record
pode ser usado?