Strings Imutáveis na Programação Java

JavaBeginner
Pratique Agora

Introdução

Neste laboratório, você aprenderá sobre os benefícios de usar Strings imutáveis na programação Java. Você entenderá por que as Strings em Java são imutáveis por padrão e como essa característica pode melhorar o desempenho geral e a segurança de suas aplicações. Ao final deste laboratório, você será capaz de criar e usar efetivamente Strings imutáveis em seus programas Java.

Criar um Arquivo de Projeto Java

Nesta etapa, você criará um novo arquivo Java chamado ImmutableStringDemo.java em seu diretório ~/project. Este arquivo conterá todo o código Java para este laboratório.

Primeiro, certifique-se de que você está no diretório ~/project. Você pode verificar seu diretório atual usando o comando pwd:

pwd

Você deverá ver uma saída semelhante a esta:

/home/labex/project

Agora, crie o arquivo ImmutableStringDemo.java usando o comando touch:

touch ImmutableStringDemo.java

Após criar o arquivo, você pode abri-lo no editor WebIDE para começar a escrever seu código Java.

Compreender a Imutabilidade de Strings e o String Pool

Em Java, os objetos String são imutáveis, o que significa que seu valor não pode ser alterado após serem criados. Essa escolha de design oferece vários benefícios, incluindo segurança, segurança de thread e otimizações de desempenho através do String pool.

O String pool é uma área de memória especial dentro do heap que armazena literais de String. Quando você cria um literal de String, o Java primeiro verifica se uma String idêntica já existe no pool. Se existir, a referência da String existente é retornada, economizando memória. Caso contrário, uma nova String é criada no pool e sua referência é retornada.

Vamos adicionar a estrutura básica de uma classe Java e demonstrar a criação de Strings. Abra ImmutableStringDemo.java no WebIDE e adicione o seguinte código:

// ~/project/ImmutableStringDemo.java
public class ImmutableStringDemo {

    public static void main(String[] args) {
        // Criando um literal de String
        String name = "LabexUser";
        System.out.println("Nome inicial: " + name);

        // Criando Strings que podem usar o String pool
        String str1 = "Hello";
        String str2 = "Hello"; // Isso provavelmente se referirá ao mesmo objeto que str1 no String pool

        System.out.println("str1: " + str1);
        System.out.println("str2: " + str2);
        System.out.println("str1 == str2: " + (str1 == str2)); // Verifica se eles se referem ao mesmo objeto

        // Criando uma String usando a palavra-chave 'new'
        // Isso sempre cria um novo objeto no heap, mesmo que o literal exista no pool
        String str3 = new String("Hello");
        System.out.println("str3: " + str3);
        System.out.println("str1 == str3: " + (str1 == str3)); // Verifica se eles se referem ao mesmo objeto
    }
}

Salve o arquivo após adicionar o código.

Agora, compile e execute o programa Java para ver a saída. No terminal, execute os seguintes comandos:

javac ImmutableStringDemo.java
java ImmutableStringDemo

Você deverá ver uma saída semelhante a esta:

Nome inicial: LabexUser
str1: Hello
str2: Hello
str1 == str2: true
str3: Hello
str1 == str3: false

Esta saída demonstra que str1 e str2 se referem ao mesmo objeto no String pool, enquanto str3 (criado com new String()) é um objeto distinto no heap, embora seu conteúdo seja idêntico.

Garantir Imutabilidade para Segurança e Segurança de Threads

A imutabilidade é crucial para a segurança e a segurança de thread em Java. Quando uma String é imutável, seu valor não pode ser alterado por nenhuma parte do programa após a criação. Isso impede que código malicioso altere dados sensíveis armazenados em Strings, como senhas ou URLs.

Além disso, objetos imutáveis são inerentemente seguros para threads. Múltiplas threads podem acessar uma String imutável concorrentemente sem a necessidade de sincronização externa, pois não há risco de uma thread modificar a String enquanto outra a está lendo.

Vamos demonstrar isso adicionando a palavra-chave final a uma String e criando um exemplo simples e seguro para threads.

Modifique seu arquivo ImmutableStringDemo.java da seguinte forma. Declararemos uma String final no nível da classe para torná-la acessível à nossa classe ThreadSafeTask.

// ~/project/ImmutableStringDemo.java
import java.util.HashMap;

public class ImmutableStringDemo {

    // Declara uma String final para segurança e segurança de thread
    private static final String SECURE_PASSWORD = "MySecurePassword123";

    // Classe interna para demonstrar segurança de thread
    static class ThreadSafeTask implements Runnable {
        private final String password; // A senha é passada como uma String imutável

        public ThreadSafeTask(String password) {
            this.password = password;
        }

        @Override
        public void run() {
            System.out.println("Thread " + Thread.currentThread().getName() + " acessando senha: " + password);
            // Simula algum trabalho
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    public static void main(String[] args) {
        // ... (o código anterior para name, str1, str2, str3 permanece aqui) ...
        String name = "LabexUser";
        System.out.println("Nome inicial: " + name);

        String str1 = "Hello";
        String str2 = "Hello";
        System.out.println("str1: " + str1);
        System.out.println("str2: " + str2);
        System.out.println("str1 == str2: " + (str1 == str2));

        String str3 = new String("Hello");
        System.out.println("str3: " + str3);
        System.out.println("str1 == str3: " + (str1 == str3));

        System.out.println("\n--- Demonstrando Segurança e Segurança de Thread ---");

        // Usando a String final
        System.out.println("Senha Segura: " + SECURE_PASSWORD);

        // Demonstra segurança de thread com a senha imutável
        for (int i = 0; i < 3; i++) {
            Thread thread = new Thread(new ThreadSafeTask(SECURE_PASSWORD), "Thread-" + (i + 1));
            thread.start();
        }

        // Aguarda as threads terminarem (opcional, para uma saída mais limpa)
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

Nota: A palavra-chave final garante que a variável SECURE_PASSWORD só possa ser atribuída uma vez. Como a própria String é imutável, isso torna o conteúdo de SECURE_PASSWORD imutável. A classe ThreadSafeTask acessa com segurança essa String imutável de múltiplas threads.

Salve o arquivo. Em seguida, compile e execute o programa novamente:

javac ImmutableStringDemo.java
java ImmutableStringDemo

Você deverá ver uma saída semelhante a esta, com as mensagens de acesso das threads aparecendo em uma ordem não determinística:

Nome inicial: LabexUser
str1: Hello
str2: Hello
str1 == str2: true
str3: Hello
str1 == str3: false

--- Demonstrando Segurança e Segurança de Thread ---
Senha Segura: MySecurePassword123
Thread Thread-1 acessando senha: MySecurePassword123
Thread Thread-2 acessando senha: MySecurePassword123
Thread Thread-3 acessando senha: MySecurePassword123

Aproveitar a Imutabilidade para Cache de Hashcode em Coleções

A imutabilidade das Strings as torna ideais para uso como chaves em coleções baseadas em hash, como HashMap e HashSet. Como o valor de uma String nunca muda, seu hashCode() pode ser computado uma vez e armazenado em cache. Chamadas subsequentes a hashCode() retornarão o valor em cache, levando a melhorias significativas de desempenho, especialmente quando Strings são frequentemente usadas como chaves.

Se as Strings fossem mutáveis, seu código hash poderia mudar após serem inseridas em um HashMap, tornando impossível recuperar o objeto usando o código hash original.

Vamos adicionar código ao ImmutableStringDemo.java para demonstrar o uso de String em um HashMap.

Adicione o seguinte código ao método main do seu arquivo ImmutableStringDemo.java, após a demonstração de segurança de thread:

// ~/project/ImmutableStringDemo.java
import java.util.HashMap; // Certifique-se de que esta importação esteja no topo do seu arquivo

public class ImmutableStringDemo {

    private static final String SECURE_PASSWORD = "MySecurePassword123";

    static class ThreadSafeTask implements Runnable {
        private final String password;

        public ThreadSafeTask(String password) {
            this.password = password;
        }

        @Override
        public void run() {
            System.out.println("Thread " + Thread.currentThread().getName() + " acessando senha: " + password);
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    public static void main(String[] args) {
        // ... (o código anterior para name, str1, str2, str3 e segurança de thread permanece aqui) ...
        String name = "LabexUser";
        System.out.println("Nome inicial: " + name);

        String str1 = "Hello";
        String str2 = "Hello";
        System.out.println("str1: " + str1);
        System.out.println("str2: " + str2);
        System.out.println("str1 == str2: " + (str1 == str2));

        String str3 = new String("Hello");
        System.out.println("str3: " + str3);
        System.out.println("str1 == str3: " + (str1 == str3));

        System.out.println("\n--- Demonstrando Segurança e Segurança de Thread ---");
        System.out.println("Senha Segura: " + SECURE_PASSWORD);

        for (int i = 0; i < 3; i++) {
            Thread thread = new Thread(new ThreadSafeTask(SECURE_PASSWORD), "Thread-" + (i + 1));
            thread.start();
        }
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }

        System.out.println("\n--- Demonstrando Cache de Hashcode ---");

        HashMap<String, Integer> studentScores = new HashMap<>();

        String studentName1 = "Alice";
        String studentName2 = "Bob";
        String studentName3 = "Alice"; // Isso se referirá à mesma "Alice" no String pool

        studentScores.put(studentName1, 95);
        studentScores.put(studentName2, 88);
        studentScores.put(studentName3, 92); // Isso atualizará o valor para "Alice"

        System.out.println("Pontuações dos Alunos: " + studentScores);
        System.out.println("Pontuação da Alice: " + studentScores.get("Alice"));
        System.out.println("Pontuação do Bob: " + studentScores.get("Bob"));

        // Demonstra que mesmo que tentemos "alterar" uma String, uma nova é criada
        String originalString = "Java";
        System.out.println("String Original: " + originalString + ", HashCode: " + originalString.hashCode());

        String modifiedString = originalString.concat(" Programming"); // Cria uma nova String
        System.out.println("String Modificada: " + modifiedString + ", HashCode: " + modifiedString.hashCode());
        System.out.println("String Original (após concat): " + originalString + ", HashCode: " + originalString.hashCode());
    }
}

Salve o arquivo. Em seguida, compile e execute o programa novamente:

javac ImmutableStringDemo.java
java ImmutableStringDemo

Você deverá ver uma saída semelhante a esta, incluindo as demonstrações de HashMap e hashcode:

Nome inicial: LabexUser
str1: Hello
str2: Hello
str1 == str2: true
str3: Hello
str1 == str3: false

--- Demonstrando Segurança e Segurança de Thread ---
Senha Segura: MySecurePassword123
Thread Thread-1 acessando senha: MySecurePassword123
Thread Thread-2 acessando senha: MySecurePassword123
Thread Thread-3 acessando senha: MySecurePassword123

--- Demonstrando Cache de Hashcode ---
Pontuações dos Alunos: {Bob=88, Alice=92}
Pontuação da Alice: 92
Pontuação do Bob: 88
String Original: Java, HashCode: 2301506
String Modificada: Java Programming, HashCode: -1479700901
String Original (após concat): Java, HashCode: 2301506

Observe como o hash code de originalString permanece o mesmo mesmo após a chamada de concat, porque concat retorna um novo objeto String, deixando o original intocado.

Resumo

Neste laboratório, você aprendeu sobre o conceito fundamental de Strings imutáveis na programação Java. Você explorou por que as Strings são imutáveis por padrão e como essa escolha de design contribui para a otimização de memória através do String pool. Você também compreendeu os benefícios críticos da imutabilidade, incluindo segurança aprimorada para dados sensíveis, segurança inerente de thread para operações concorrentes e desempenho aprimorado em coleções baseadas em hash devido ao cache de hashcode. Ao completar os exercícios práticos, você adquiriu experiência prática na criação e utilização eficaz de Strings imutáveis em suas aplicações Java.