Como Criar uma Classe Imutável em Java

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.

Como um objeto imutável não pode ser atualizado, os programas precisam criar um novo objeto para cada mudança de estado. No entanto, objetos imutáveis também têm os seguintes benefícios:

  • Uma classe imutável é boa para fins de armazenamento em cache, pois você não precisa se preocupar com as mudanças de valor.
  • Uma classe imutável é inerentemente segura para threads, portanto, você não precisa se preocupar com a segurança de threads em ambientes de várias threads.

Saiba mais sobre multithreading em Java e explore as Perguntas de Entrevista sobre Multithreading em Java.

Criando uma Classe Imutável em Java

Para criar uma classe imutável em Java, você precisa seguir estes princípios gerais:

  1. Declare a classe como final para que ela não possa ser estendida.
  2. Torne todos os campos private para que o acesso direto não seja permitido.
  3. Não forneça métodos setter para as variáveis.
  4. Torne todos os campos mutáveis final para que o valor do campo possa ser atribuído apenas uma vez.
  5. Inicialize todos os campos usando um método construtor realizando cópia profunda.
  6. Realize clonagem de objetos nos métodos getter para retornar uma cópia em vez de retornar a referência real do objeto.

A classe a seguir é um exemplo que ilustra os fundamentos da 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:

FinalClassExample.java
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 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 das variáveis locais
		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());

	}

}

Compile e execute o programa:

  1. javac FinalClassExample.java
  2. java FinalClassExample

Nota: Você pode receber a seguinte mensagem ao compilar o arquivo: Nota: FinalClassExample.java está usando operações não verificadas ou inseguras porque o método getter está usando um cast não verificado de HashMap<String, String> para Object. Você pode ignorar o aviso do compilador para os propósitos deste exemplo.

Você obtém a seguinte saída:

Output
Performing 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 foram alterados porque o construtor usa 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, destacado no exemplo a seguir.
  • No método getter, exclua return (HashMap<String, String>) testMap.clone(); e adicione return testMap;.

O arquivo de exemplo deve ficar assim:

FinalClassExample.java
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:

  1. javac FinalClassExample.java
  2. java FinalClassExample

Output
Performing 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 cópia rasa, existe uma referência direta ao objeto original na função getter.

Conclusão

Você aprendeu alguns dos princípios gerais a seguir quando cria classes imutáveis em Java, incluindo a importância da cópia profunda. Continue seu aprendizado com mais tutoriais de Java.

Source:
https://www.digitalocean.com/community/tutorials/how-to-create-immutable-class-in-java