Dominando os Fundamentos de Java: Sintaxe, POO e Manipulação de Dados

O Ecossistema Java no Desenvolvimento Corporativo

O Java mantém sua posição como uma das linguagens mais robustas e requisitadas para o desenvolvimento de software empresarial. Sua vasta ekosistema, impulsoinado por frameworks como Spring e Spring Boot, e sua compatibilidade com arquiteturas distribuídas (como Spring Cloud e microsserviços), garantem sua hegemonia no backend. Embora versões mais recentes como o JDK 21 tragam inovações significativas, o JDK 8 ainda é amplamnete utilizado em sistemas legados e corporativos devido à sua estabilidade e suporte a ferramentas como o Seata para transações distribuídas.

Variáveis, Tipos de Dados e Memória

Em Java, as variáveis são divididas em dois grandes grupos: tipos primitivos e tipos de referência. A principal diferença entre eles reside na forma como a memória é alocada.

  • Tipos Primitivos: Armazenam diretamente o valor na memória Stack (Pilha). Não são objetos, mas possuem suas respectivas classes Wrapper (como Integer, Double) para quando for necessário tratá-los como objetos.
  • Tipos de Referência: A referência (o endereço de memória) é armazenada na Stack, mas o objeto em si é instanciado na memória Heap.

Operadores Lógicos e Bit a Bit

Além dos operadores lógicos tradicionais (&&, ||, !) que operam sobre booleanos, Java possui operadores bit a bit que manipulam diretamente a representação binária dos números inteiros.

public class OperadoresExemplo {
    public static void main(String[] args) {
        int valorA = 12; // 1100 em binário
        int valorB = 10; // 1010 em binário
        
        // Operações bit a bit
        System.out.println("E bit a bit: " + (valorA & valorB)); // 1000 -> 8
        System.out.println("Ou bit a bit: " + (valorA | valorB)); // 1110 -> 14
        System.out.println("XOR bit a bit: " + (valorA ^ valorB)); // 0110 -> 6
        
        // Deslocamento
        System.out.println("Deslocamento esquerda (x4): " + (valorA << 2)); // 48
        System.out.println("Deslocamento direita (/4): " + (valorB >> 2)); // 2
    }
}

Estruturas de Controle e Arrays

O controle de fluxo em Java utiliza construções padrão como if-else, switch, e laços de repetição (for, while, do-while, e o foreach para coleções e arrays).

Modelo de Memória de Arrays

Arrays em Java são objetos. O motivo pelo qual a indexação começa em 0 está ligado à otimização de acesso à memória. O cálculo do endereço de um elemento é feito através da fórmula: Endereço Base + (indice * tamanho_do_tipo). Começar do zero elimina a necessidade de subtrair 1 em cada acesso, tornando a leitura mais rápida.

import java.util.Arrays;

public class ManipulacaoArrays {
    public static void main(String[] args) {
        double[] temperaturas = {22.5, 24.0, 19.8, 25.3, 21.1};
        
        // Encontrar a temperatura máxima
        double maxTemp = temperaturas[0];
        for (double temp : temperaturas) {
            if (temp > maxTemp) {
                maxTemp = temp;
            }
        }
        System.out.println("Temperatura máxima registrada: " + maxTemp);
        
        // Utilitários da classe Arrays
        Arrays.sort(temperaturas);
        int indice = Arrays.binarySearch(temperaturas, 24.0);
        System.out.println("Índice da temp 24.0 após ordenação: " + indice);
    }
}

Programação Orientada a Objetos (POO)

A POO em Java é estruturada em torno de classes e objetos, aplicando os pilares do encapsulamento, herança, polimorfismo e abstração.

Encapsulamento e Construtores

O encapsulamento protege o estado interno do objeto, exigindo que a interação ocorra através de métodos públicos (getters e setters), permitindo a validação de dados. A palavra-chave this é utilizada para diferenciar variáveis de instância de parâmetros de métodos ou construtores.

Herança, Polimorfismo e Interfaces

A herança (extends) permite o reuso de código, enquanto o polimorfismo permite que uma referência de um tipo pai aponte para objetos de tipos filhos, executando a implementação específica do filho em tempo de execução. Interfaces (implements) elevam o nível de abstração, definindo contratos que desacoplam a arquitetura do sistema.

// Contrato (Interface)
public interface Notificador {
    void enviar(String mensagem, String destinatario);
}

// Classe Abstrata com implementação parcial
public abstract class NotificadorBase implements Notificador {
    protected String remetente;
    
    public NotificadorBase(String remetente) {
        this.remetente = remetente;
    }
    
    protected void logEnvio(String destinatario) {
        System.out.println("[LOG] Mensagem despachada para " + destinatario + " via " + this.getClass().getSimpleName());
    }
}

// Implementação Concreta 1
public class NotificadorEmail extends NotificadorBase {
    public NotificadorEmail(String remetente) {
        super(remetente);
    }
    
    @Override
    public void enviar(String mensagem, String destinatario) {
        System.out.println("Enviando E-mail para " + destinatario + ": " + mensagem);
        logEnvio(destinatario);
    }
}

// Implementação Concreta 2
public class NotificadorSMS extends NotificadorBase {
    public NotificadorSMS(String remetente) {
        super(remetente);
    }
    
    @Override
    public void enviar(String mensagem, String destinatario) {
        System.out.println("Enviando SMS para " + destinatario + ": " + mensagem);
        logEnvio(destinatario);
    }
}

// Uso do Polimorfismo e Injeção de Dependência
public class ServicoNotificacao {
    private Notificador notificador;
    
    public void setNotificador(Notificador notificador) {
        this.notificador = notificador;
    }
    
    public void notificarUsuario(String msg, String user) {
        if (notificador != null) {
            notificador.enviar(msg, user);
        }
    }
    
    public static void main(String[] args) {
        ServicoNotificacao servico = new ServicoNotificacao();
        
        // Injetando implementação de Email
        servico.setNotificador(new NotificadorEmail("sistema@empresa.com"));
        servico.notificarUsuario("Bem-vindo!", "usuario@test.com");
        
        // Injetando implementação de SMS
        servico.setNotificador(new NotificadorSMS("AlertasCorp"));
        servico.notificarUsuario("Código de verificação: 4092", "+5511999999999");
    }
}

A Classe Object e os Contratos de Igualdade

Toda classe em Java herda implicitamente da classe java.lang.Object. Dois dos métodos mais críticos que frequentemente precisam ser sobrescritos são equals() e hashCode().

  • equals(): Define a lógica de igualdade baseada no valor lógico dos atributos, diferentemente do operador == que compara endereços de memória.
  • hashCode(): Retorna um valor inteiro derivado do estado do objeto. É fundamental para o funcionamento correto de coleções baseadas em hash (como HashSet e HashMap).

Uma regra imutável em Java é: se dois objetos são considerados iguais pelo método equals(), eles obrigatoriamente devem retornar o mesmo valor no hashCode().

import java.util.Objects;

public class Produto {
    private String sku;
    private String nome;
    private double preco;

    public Produto(String sku, String nome, double preco) {
        this.sku = sku;
        this.nome = nome;
        this.preco = preco;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Produto produto = (Produto) o;
        // Igualdade definida apenas pelo SKU (identificador único)
        return Objects.equals(sku, produto.sku);
    }

    @Override
    public int hashCode() {
        // O hashCode deve usar os mesmos atributos que definem o equals
        return Objects.hash(sku);
    }
    
    @Override
    public String toString() {
        return "Produto{SKU='" + sku + "', Nome='" + nome + "', Preço=" + preco + "}";
    }
}

Tags: java programacao-orientada-a-objetos sintaxe-java api-java estruturas-de-dados

Publicado em 6-23 23:04