VapVup👤
← Voltar

Fundamentos da Linguagem Java

Java é uma linguagem fortemente tipada, orientada a objetos e compilada para bytecode que roda na JVM. Mesmo após 8+ anos de experiência, os fundamentos podem enferrujar — especialmente quando frameworks e IA fazem o trabalho pesado. Este módulo revisita as bases com profundidade técnica: tipos, operadores, OOP, interfaces, records e imutabilidade.

👟Analogia

Primitivos vs Wrappers — Caixa de Sapato

Um primitivo é o sapato em si — leve, direto, sem embalagem. Um wrapper é o sapato DENTRO da caixa — ocupa mais espaço (heap ao invés de stack), mas pode ser colocado na prateleira genérica (coleções, generics). Autoboxing é o ato de colocar o sapato na caixa automaticamente; unboxing é tirar. O problema? Se você ficar colocando e tirando o sapato da caixa milhões de vezes num loop, vai perder um tempo absurdo sem perceber.

💡Conceito

Tipos Primitivos — Os 8 Fundamentos

Java tem exatamente 8 tipos primitivos: byte (8 bits, -128 a 127), short (16 bits), int (32 bits), long (64 bits), float (32 bits IEEE 754), double (64 bits IEEE 754), char (16 bits unsigned, Unicode), boolean. Cada um tem um wrapper correspondente (Byte, Short, Integer, Long, Float, Double, Character, Boolean). Wrappers são objetos — vivem no heap, podem ser null, e são necessários para coleções genéricas (List<Integer>, não List<int>). A pegadinha clássica de entrevista: Integer cacheia valores de -128 a 127 (IntegerCache), fazendo == funcionar nesses valores. Fora desse range, == compara referências e falha.

🔧Exemplo

IntegerCache — A Armadilha do ==

Integer a = 127;
Integer b = 127;
System.out.println(a == b);    // true (mesma referência do cache)

Integer c = 128;
Integer d = 128;
System.out.println(c == d);    // false (objetos distintos!)
System.out.println(c.equals(d)); // true (compara valores)

// Regra de ouro: SEMPRE use .equals() para comparar Wrappers
// == é para primitivos e verificação de identidade (mesmo objeto)
🔑Analogia

Passagem por Valor — O Endereço no Papel

Imagine que você tem a chave de casa (objeto). Quando passa para um amigo (método), você não dá a chave original — você faz uma CÓPIA da chave. O amigo pode entrar na casa e mover os móveis (alterar atributos do objeto). Mas se ele jogar a cópia da chave fora e pegar outra (reatribuir a referência), a sua chave original não muda. Java SEMPRE passa por valor: para primitivos, copia o valor; para objetos, copia a referência. Nunca passa a referência em si.

💡Conceito

Operadores e Controle de Fluxo

Além dos operadores básicos (+, -, *, /), Java tem operadores bitwise (&, |, ^, ~, <<, >>, >>>) e o operador ternário (cond ? a : b). Cuidado com & vs && e | vs ||: as versões duplas fazem short-circuit (não avaliam o segundo operando se o resultado já é determinado). Switch expressions (Java 14+) com arrow syntax (->) eliminam fall-through e retornam valores diretamente. Labels em loops permitem break/continue direcionado a um loop externo específico — raro, mas útil.

🔧Exemplo

Switch Expressions — Moderno e Seguro

// Switch expression (Java 14+) — retorna valor, sem fall-through
String statusText = switch (statusCode) {
    case 200 -> "OK";
    case 404 -> "Not Found";
    case 500 -> {
        log.error("Server error");
        yield "Internal Server Error";  // yield para blocos
    }
    default -> "Unknown: " + statusCode;
};

// Sem break, sem fall-through acidental, exhaustive check
// O compilador garante que TODOS os casos são cobertos
🧬Analogia

Herança vs Composição — DNA vs Ferramentas

Herança é como DNA — você nasce com ele e não pode trocar. Se seu pai é um pato, você é um pato (Pato extends Ave). Composição é como ferramentas no cinto — você pode trocar a qualquer momento. Um mecânico TEM uma chave inglesa, mas pode trocá-la por uma chave de fenda. Na dúvida, prefira composição: 'Favor composition over inheritance' (Design Patterns, GoF). Herança cria acoplamento forte e é decidida em compile-time. Composição é flexível e decidida em runtime.

💡Conceito

Polimorfismo e Binding Dinâmico

Polimorfismo em Java se manifesta de duas formas: (1) Estático (compile-time) — sobrecarga (overloading): o compilador resolve qual método chamar baseado nos tipos dos parâmetros. (2) Dinâmico (runtime) — sobrescrita (overriding): a JVM resolve qual implementação chamar baseado no tipo REAL do objeto, não no tipo da referência. O binding dinâmico é o coração do OOP — permite que 'Animal a = new Cachorro()' chame Cachorro.falar() e não Animal.falar(). Nota: métodos static e private NÃO participam de binding dinâmico.

🔧Exemplo

Records — Imutabilidade Concisa (Java 16+)

// Record = classe imutável concisa para dados
record Coordenada(double lat, double lng) {
    // Compact constructor para validação
    Coordenada {
        if (lat < -90 || lat > 90)
            throw new IllegalArgumentException("Latitude inválida");
        if (lng < -180 || lng > 180)
            throw new IllegalArgumentException("Longitude inválida");
    }
}

// Gerados automaticamente:
// - Constructor canônico
// - lat(), lng() (accessors sem 'get')
// - equals(), hashCode() baseados em todos os componentes
// - toString() → Coordenada[lat=23.55, lng=-46.63]

var sp = new Coordenada(-23.55, -46.63);
var sp2 = new Coordenada(-23.55, -46.63);
sp.equals(sp2); // true — value-based equality
📜Analogia

Imutabilidade — Documento Tabelionado

Um objeto imutável é como um documento registrado em cartório — depois de registrado, ninguém pode alterá-lo. Se você precisa de uma versão diferente, cria um NOVO documento. String em Java funciona assim: 'str.toUpperCase()' não modifica str — cria uma nova String. A vantagem? Thread-safety grátis (ninguém modifica), cacheabilidade (hashCode calculado uma vez), e segurança (credenciais não podem ser alteradas por código malicioso). O custo? Criar novos objetos ao invés de modificar — por isso StringBuilder existe para concatenações em loop.