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 (comoHashSeteHashMap).
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 + "}";
}
}