Olá!
Agora vamos falar sobre classes imutáveis em Java e mostrar como tornar a classe imutável.
A classe é chamada imutável se for impossível alterar seu estado e conteúdo após a inicialização (criação).

Os benefícios de tais classes são:
- segurança: você, como desenvolvedor, pode ter certeza de que ninguém é capaz de mudar seu estado;
 - thread-safety: o mesmo mencionado acima também é real para o ambiente multithread;
 - armazenável em cache: as instâncias podem ser armazenadas em cache facilmente pelo cache da VM ou pela implementação customizada, pois temos 100% de certeza de que seus valores não serão alterados;
 - hashable: tais classes podem ser seguramente colocadas dentro das coleções de hash (como 
HashMap,HashSetetc) – é claro, se os métodosequals()&hashCode()são substituídos de maneira apropriada. 
Então, vamos tentar implementar a classe imutável pegando o mutável e mudando sua implementação para o imutável passo a passo.
Considere, temos a seguinte classe POJO típica Hobbit:
import java.util.List;
public class Hobbit {
    private String name;
    private Address address;
    private List<string> stuff;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Address getAddress() {
        return address;
    }
    public void setAddress(Address address) {
        this.address = address;
    }
    public List<string> getStuff() {
        return stuff;
    }
    public void setStuff(List<string> stuff) {
        this.stuff = stuff;
    }
}Hobbitclasse tem um campo addressdo tipo Address:
public class Address {
    private String country;
    private String city;
    public Address(String country, String city) {
        this.country = country;
        this.city = city;
    }
    public String getCountry() {
        return country;
    }
    public void setCountry(String country) {
        this.country = country;
    }
    public String getCity() {
        return city;
    }
    public void setCity(String city) {
        this.city = city;
    }
}Address A classe não será alterada neste artigo, portanto, em caso de dúvidas, consulte o código incorporado acima.
A primeira questão a ser notada são setters, que permitem alterar o valor de cada campo de classe. Então, vamos remover esses métodos:
1. removendo setters
public class Hobbit {
    private String name;
    private Address address;
    private List<String> stuff;
    
    // getters:
    public String getName() {
        return name;
    }
    public Address getAddress() {
        return address;
    }
    public List<String> getStuff() {
        return stuff;
    }
}Parece melhor, mas agora o usuário de nossa classe não tem chances de definir os valores para campos de classe. Então, vamos fornecer o construtor apropriado:
2. adicionando todos os construtores args
public class Hobbit {
    private String name;
    private Address address;
    private List<String> stuff;
    
    // all args constructor:
    public Hobbit(String name, Address address, List<String> stuff) {
        this.name = name;
        this.address = address;
        this.stuff = stuff;
    }
    // getters:
    public String getName() {
        return name;
    }
    public Address getAddress() {
        return address;
    }
    public List<String> getStuff() {
        return stuff;
    }
}Nós terminamos agora? Ainda não, restam vários problemas. Primeiro de tudo, nossa imutabilidade poderia ser hackeada usando um dos princípios da OOP – herança. Deixe-me demonstrar isso com um exemplo simples:
import java.util.List;
public class Gandalf extends Hobbit {
    private String hackedName;
    public Gandalf(String name, Address address, List<String> stuff) {
        super(name, address, stuff);
        hackedName = name;
    }
    public void hackTheImmutability(String newNameValue) {
        hackedName = newNameValue;
        System.out.println("Immutability has been hacked!");
    }
    @Override
    public String getName() {
        return hackedName;
    }
}import java.util.Collections;
public class Hack {
    public static void main(String[] args) {
        Gandalf gandalf = new Gandalf("Frodo Baggins", new Address("Hobitton", "Shire"), Collections.emptyList());
        Hobbit hobbit = (Hobbit) gandalf;
        
        System.out.println(hobbit.getName());
      
        System.out.println();
        gandalf.hackTheImmutability("Mr. Underhill");
        System.out.println();
      
        System.out.println(hobbit.getName());
    }
}Se executarmos a Hackturma, obteremos a seguinte saída:
Frodo Bolseiro
Imutabilidade foi hackeada!
Mr. Underhill
Para corrigir esse problema, precisamos:
3. classe de marcação como final para protegê-la de ser estendida
public final class Hobbit {
    private String name;
    private Address address;
    private List<String> stuff;
    
    // all args constructor:
    public Hobbit(String name, Address address, List<String> stuff) {
        this.name = name;
        this.address = address;
        this.stuff = stuff;
    }
    // getters:
    public String getName() {
        return name;
    }
    public Address getAddress() {
        return address;
    }
    public List<String> getStuff() {
        return stuff;
    }
}Agora, se executarmos a Hackaula mais uma vez, a saída estará correta:
Frodo Bolseiro
Imutabilidade foi hackeada!
Frodo Bolseiro
Há mais uma questão interessante: como sabemos, em Java quando passamos o valor de um objeto, estamos passando a referência a ele. Assim, o estado dos objetos poderia ser alterado pelos efeitos colaterais. Vamos voltar ao código para ver isso:
import java.util.List;
import java.util.ArrayList;
public class Hack {
    public static void main(String[] args) {
        Address address = new Address("Hobitton", "Shire");
        List<String> stuff = new ArrayList<>();
        stuff.add("Sword");
        stuff.add("Ring of Power");
        Hobbit hobbit = new Hobbit("Frodo Baggins", address, stuff);
        System.out.println("Hobbit country: " + hobbit.getAddress().getCountry());
        System.out.println("Hobbit city: " + hobbit.getAddress().getCity());
        System.out.println("Hobbit stuff: " + hobbit.getStuff());
        address.setCountry("Isengard");
        address.setCity("Saruman tower");
        stuff.remove("Ring of Power");
        stuff.remove("Sword"); 
        
        System.out.println();
        System.out.println("Immutability has been hacked!");
        System.out.println();
        
        System.out.println("Hobbit country: " + hobbit.getAddress().getCountry());
        System.out.println("Hobbit city: " + hobbit.getAddress().getCity());
        System.out.println("Hobbit stuff: " + hobbit.getStuff());
    }
}A saída deste código é:
Hobbit country: Hobitton
Hobbit city: Shire
Hobbit stuff: [Espada, Anel do Poder]
Imutabilidade foi hackeada!
Hobbit country: Isengard
Cidade de Hobbit: Saruman tower
Hobbit stuff: []
Esse problema pode ser corrigido por:
4. inicializando todos os campos mutáveis não-primitivos via construtor executando uma cópia profunda
import java.util.List;
import java.util.ArrayList;
public final class Hobbit {
    private String name;
    private Address address;
    private List<String> stuff;
    // all args constructor:
    public Hobbit(String name, Address address, List<String> stuff) {
        this.name = name;
        this.address = new Address(address.getCountry(), address.getCity());
        this.stuff = new ArrayList<>(stuff);
    }
    // getters:
    public String getName() {
        return name;
    }
    public Address getAddress() {
        return address;
    }
    public List<String> getStuff() {
        return stuff;
    }
}Agora a saída da Hackclasse é mais previsível:
Hobbit country: Hobitton
Hobbit city: Shire
Hobbit stuff: [Espada, Anel do Poder]
Imutabilidade foi hackeada!
Hobbit country: Hobitton
Hobbit city: Shire
Hobbit stuff: [Espada, Anel do Poder]
Mas o mesmo problema de efeito colateral poderia ser produzido através de métodos getter:
import java.util.ArrayList;
import java.util.List;
public class Hack {
    public static void main(String[] args) {
        Address address = new Address("Hobitton", "Shire");
        List<String> stuff = new ArrayList<>();
        stuff.add("Sword");
        stuff.add("Ring of Power");
        Hobbit hobbit = new Hobbit("Frodo Baggins", address, stuff);
        System.out.println("Hobbit country: " + hobbit.getAddress().getCountry());
        System.out.println("Hobbit city: " + hobbit.getAddress().getCity());
        System.out.println("Hobbit stuff: " + hobbit.getStuff());
        Address hobbitAddress = hobbit.getAddress();
        hobbitAddress.setCountry("Isengard");
        hobbitAddress.setCity("Saruman tower");
        List<String> hobbitStuff = hobbit.getStuff();
        hobbitStuff.remove("Ring of Power");
        hobbitStuff.remove("Sword");
        
        System.out.println();
        System.out.println("Immutability has been hacked!");
        System.out.println();
        System.out.println("Hobbit country: " + hobbit.getAddress().getCountry());
        System.out.println("Hobbit city: " + hobbit.getAddress().getCity());
        System.out.println("Hobbit stuff: " + hobbit.getStuff());
    }
}Saída:
Hobbit country: Hobitton
Hobbit city: Shire
Hobbit stuff: [Espada, Anel do Poder]
Imutabilidade foi hackeada!
Hobbit country: Isengard
Cidade de Hobbit: Saruman tower
Hobbit stuff: []
Então, vai com outro passo a ser feito:
5. executando clonagem do objeto mutável não-primitivo retornado em métodos getter
import java.util.ArrayList;
import java.util.List;
public final class Hobbit {
    private String name;
    private Address address;
    private List<String> stuff;
    // all args constructor:
    public Hobbit(String name, Address address, List<String> stuff) {
        this.name = name;
        this.address = new Address(address.getCountry(), address.getCity());
        this.stuff = new ArrayList<>(stuff);
    }
    // getters:
    public String getName() {
        return name;
    }
    public Address getAddress() {
        return new Address(address.getCountry(), address.getCity());
    }
    public List<String> getStuff() {
        return new ArrayList<>(stuff);
    }
}Agora a Hacksaída é:
Hobbit country: Hobitton
Hobbit city: Shire
Hobbit stuff: [Espada, Anel do Poder]
Imutabilidade foi hackeada!
Hobbit country: Hobitton
Hobbit city: Shire
Hobbit stuff: [Espada, Anel do Poder]
Há mais uma etapa opcional a ser executada, mas se todos os outros pré-requisitos forem atendidos, isso não é necessário. De qualquer forma, vamos mencionar isso:
6. marcando todos os campos de classe como final (opcional)
import java.util.ArrayList;
import java.util.List;
public final class Hobbit {
    private final String name;
    private final Address address;
    private final List<String> stuff;
    // all args constructor:
    public Hobbit(String name, Address address, List<String> stuff) {
        this.name = name;
        this.address = new Address(address.getCountry(), address.getCity());
        this.stuff = new ArrayList<>(stuff);
    }
    // getters:
    public String getName() {
        return name;
    }
    public Address getAddress() {
        return new Address(address.getCountry(), address.getCity());
    }
    public List<String> getStuff() {
        return new ArrayList<>(stuff);
    }
}E é isso, estamos prontos – nossa Hobbitclasse é imutável agora.
Vamos resumir os passos:
- remover setters;
 - adicionando todo o construtor args;
 - marcando a classe de modo 
finala protegê-la de ser estendida; - inicializar todos os campos mutáveis não primitivos via construtor executando uma cópia profunda;
 - executando clonagem do objeto mutável não-primitivo retornado nos métodos getter;
 - marcando todos os campos de classe como 
final(etapa opcional). 
Claro, existem outras maneiras de tornar a classe imutável (por exemplo, usando o padrão Builder), mas está fora do escopo da história atual.