Introdução
Este artigo fornece uma visão geral de como criar uma classe imutável na programação Java.
Um objeto é imutável quando seu estado não muda após ter sido inicializado. Por exemplo, String
é uma classe imutável e, uma vez instanciado, o valor de um objeto String
nunca muda. Saiba mais sobre por que a classe String
é imutável em Java.
Porque um objeto imutável não pode ser atualizado, os programas precisam criar um novo objeto para cada mudança de estado. No entanto, os objetos imutáveis também têm os seguintes benefícios:
- Uma classe imutável é boa para fins de armazenamento em cache porque você não precisa se preocupar com as mudanças de valor.
- Uma classe imutável é inerentemente segura para threads, então você não precisa se preocupar com a segurança de threads em ambientes multi-threaded.
Saiba mais sobre multi-threading em Java e navegue pelas Questões de Entrevista sobre Multi-Threading em Java.
Criando uma Classe Imutável em Java
Para criar uma classe imutável em Java, você precisa seguir estes princípios gerais:
- Declare a classe como
final
para que não possa ser estendida. - Torne todos os campos
private
para que o acesso direto não seja permitido. - Não forneça métodos setter para as variáveis.
- Torne todos os campos mutáveis
final
para que o valor de um campo possa ser atribuído apenas uma vez. - Inicialize todos os campos usando um método construtor realizando uma cópia profunda.
- Realize clonagem de objetos nos métodos getter para retornar uma cópia em vez de retornar a referência real do objeto.
A seguinte classe é um exemplo que ilustra os conceitos básicos de imutabilidade. A classe FinalClassExample
define os campos e fornece o método construtor que utiliza cópia profunda para inicializar o objeto. O código no método main
do arquivo FinalClassExample.java
testa a imutabilidade do objeto.
Crie um novo arquivo chamado FinalClassExample.java
e copie o seguinte código:
import java.util.HashMap;
import java.util.Iterator;
public final class FinalClassExample {
// campos da classe FinalClassExample
private final int id;
private final String name;
private final HashMap<String,String> testMap;
public int getId() {
return id;
}
public String getName() {
return name;
}
// Função Getter para objetos mutáveis
public HashMap<String, String> getTestMap() {
return (HashMap<String, String>) testMap.clone();
}
// Método construtor realizando cópia profunda
public FinalClassExample(int i, String n, HashMap<String,String> hm){
System.out.println("Performing Deep Copy for Object initialization");
// A palavra-chave "this" refere-se ao objeto atual
this.id=i;
this.name=n;
HashMap<String,String> tempMap=new HashMap<String,String>();
String key;
Iterator<String> it = hm.keySet().iterator();
while(it.hasNext()){
key=it.next();
tempMap.put(key, hm.get(key));
}
this.testMap=tempMap;
}
// Testar a classe imutável
public static void main(String[] args) {
HashMap<String, String> h1 = new HashMap<String,String>();
h1.put("1", "first");
h1.put("2", "second");
String s = "original";
int i=10;
FinalClassExample ce = new FinalClassExample(i,s,h1);
// imprimir os valores de ce
System.out.println("ce id: "+ce.getId());
System.out.println("ce name: "+ce.getName());
System.out.println("ce testMap: "+ce.getTestMap());
// alterar os valores da variável local
i=20;
s="modified";
h1.put("3", "third");
// imprimir os valores novamente
System.out.println("ce id after local variable change: "+ce.getId());
System.out.println("ce name after local variable change: "+ce.getName());
System.out.println("ce testMap after local variable change: "+ce.getTestMap());
HashMap<String, String> hmTest = ce.getTestMap();
hmTest.put("4", "new");
System.out.println("ce testMap after changing variable from getter methods: "+ce.getTestMap());
}
}
Compilar e executar o programa:
- javac FinalClassExample.java
- java FinalClassExample
Nota: Pode receber a seguinte mensagem ao compilar o arquivo: Nota: FinalClassExample.java utiliza operações não verificadas ou inseguras
porque o método Getter está usando uma conversão não verificada de HashMap<String, String>
para Object
. Pode ignorar o aviso do compilador para os propósitos deste exemplo.
Obtém a seguinte saída:
OutputPerforming Deep Copy for Object initialization
ce id: 10
ce name: original
ce testMap: {1=first, 2=second}
ce id after local variable change: 10
ce name after local variable change: original
ce testMap after local variable change: {1=first, 2=second}
ce testMap after changing variable from getter methods: {1=first, 2=second}
A saída mostra que os valores do HashMap não mudaram porque o construtor utiliza cópia profunda e a função Getter retorna um clone do objeto original.
O que acontece quando você não usa cópia profunda e clonagem
Você pode fazer alterações no arquivo FinalClassExample.java
para mostrar o que acontece quando você usa cópia rasa em vez de cópia profunda e retorna o objeto em vez de uma cópia. O objeto não é mais imutável e pode ser alterado. Faça as seguintes alterações no arquivo de exemplo (ou copie e cole do exemplo de código):
- Exclua o método construtor que fornece cópia profunda e adicione o método construtor que fornece cópia rasa que está destacado no seguinte exemplo.
- No método getter, exclua
return (HashMap<String, String>) testMap.clone();
e adicionereturn testMap;
.
O arquivo de exemplo deve agora ficar assim:
import java.util.HashMap;
import java.util.Iterator;
public final class FinalClassExample {
// campos da classe FinalClassExample
private final int id;
private final String name;
private final HashMap<String,String> testMap;
public int getId() {
return id;
}
public String getName() {
return name;
}
// Função getter para objetos mutáveis
public HashMap<String, String> getTestMap() {
return testMap;
}
// Método construtor realizando cópia rasa
public FinalClassExample(int i, String n, HashMap<String,String> hm){
System.out.println("Performing Shallow Copy for Object initialization");
this.id=i;
this.name=n;
this.testMap=hm;
}
// Teste a classe imutável
public static void main(String[] args) {
HashMap<String, String> h1 = new HashMap<String,String>();
h1.put("1", "first");
h1.put("2", "second");
String s = "original";
int i=10;
FinalClassExample ce = new FinalClassExample(i,s,h1);
// imprima os valores ce
System.out.println("ce id: "+ce.getId());
System.out.println("ce name: "+ce.getName());
System.out.println("ce testMap: "+ce.getTestMap());
// altere os valores da variável local
i=20;
s="modified";
h1.put("3", "third");
// imprima os valores novamente
System.out.println("ce id after local variable change: "+ce.getId());
System.out.println("ce name after local variable change: "+ce.getName());
System.out.println("ce testMap after local variable change: "+ce.getTestMap());
HashMap<String, String> hmTest = ce.getTestMap();
hmTest.put("4", "new");
System.out.println("ce testMap after changing variable from getter methods: "+ce.getTestMap());
}
}
Compile e execute o programa:
- javac FinalClassExample.java
- java FinalClassExample
Você obtém a seguinte saída:
OutputPerforming Shallow Copy for Object initialization
ce id: 10
ce name: original
ce testMap: {1=first, 2=second}
ce id after local variable change: 10
ce name after local variable change: original
ce testMap after local variable change: {1=first, 2=second, 3=third}
ce testMap after changing variable from getter methods: {1=first, 2=second, 3=third, 4=new}
A saída mostra que os valores do HashMap foram alterados porque o método construtor usa uma cópia superficial, havendo uma referência direta ao objeto original na função getter.
Conclusão
Você aprendeu alguns dos princípios gerais a seguir ao criar classes imutáveis em Java, incluindo a importância da cópia profunda. Continue sua aprendizagem com mais tutoriais de Java.
Source:
https://www.digitalocean.com/community/tutorials/how-to-create-immutable-class-in-java