Java Moderno
A partir do Java 8, a linguagem passou por uma transformação significativa com a adição de recursos de programação funcional. Desde então, cada versão trouxe novidades que tornam o código mais expressivo e conciso.
Lambda Expressions
Seção intitulada “Lambda Expressions”Lambdas permitem tratar funções como valores - criando implementações de interfaces funcionais (interfaces com um único método abstrato) de forma inline:
// Antes do Java 8Runnable r = new Runnable() { @Override public void run() { System.out.println("Executando..."); }};
// Com lambdaRunnable r = () -> System.out.println("Executando...");A sintaxe é: (parâmetros) -> { corpo }. Quando o corpo tem uma única expressão, as chaves e o return são opcionais:
// Um parâmetro, expressão simplesList<String> nomes = List.of("Ana", "Bruno", "Carlos");nomes.forEach(nome -> System.out.println(nome));
// Dois parâmetrosComparator<String> comp = (a, b) -> a.compareTo(b);
// Com blocoRunnable r = () -> { System.out.println("Linha 1"); System.out.println("Linha 2");};Interfaces funcionais comuns
Seção intitulada “Interfaces funcionais comuns”import java.util.function.*;
Predicate<String> isEmpty = s -> s.isEmpty(); // booleanFunction<String, Integer> length = s -> s.length(); // A -> BConsumer<String> print = s -> System.out.println(s); // voidSupplier<String> hello = () -> "Olá"; // -> ABiFunction<Integer, Integer, Integer> soma = (a, b) -> a + b;Method Reference
Seção intitulada “Method Reference”Method reference (::) é uma forma ainda mais concisa de referenciar métodos existentes quando o lambda apenas delega a chamada:
// Lambda equivalente ao método existentenomes.forEach(nome -> System.out.println(nome));
// Method reference - mais limponomes.forEach(System.out::println);Quatro tipos:
// Método estáticoFunction<String, Integer> parse = Integer::parseInt;
// Método de instância de tipo arbitrárioFunction<String, String> upper = String::toUpperCase;
// Método de instância de objeto específicoString prefixo = "Java: ";Function<String, String> adicionar = prefixo::concat;
// ConstrutorSupplier<ArrayList<String>> criarLista = ArrayList::new;Streams API
Seção intitulada “Streams API”Streams permitem processar sequências de elementos de forma declarativa e funcional. Não armazenam dados - são pipelines de operações.
import java.util.stream.*;
List<Integer> numeros = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Pipeline: filtrar pares, elevar ao quadrado, coletarList<Integer> resultado = numeros.stream() .filter(n -> n % 2 == 0) // intermediária .map(n -> n * n) // intermediária .collect(Collectors.toList()); // terminal// [4, 16, 36, 64, 100]Operações intermediárias (lazy - executam só quando há uma terminal)
Seção intitulada “Operações intermediárias (lazy - executam só quando há uma terminal)”stream.filter(Predicate) // filtra elementosstream.map(Function) // transforma cada elementostream.flatMap(Function) // transforma e achata coleções aninhadasstream.sorted() // ordenastream.sorted(Comparator) // ordena com critériostream.distinct() // remove duplicatasstream.limit(n) // limita a n elementosstream.skip(n) // pula n elementosstream.peek(Consumer) // inspeciona sem alterar (útil para debug)Operações terminais (disparam o pipeline)
Seção intitulada “Operações terminais (disparam o pipeline)”stream.collect(Collectors.toList()) // coleta em listastream.collect(Collectors.toSet()) // coleta em setstream.collect(Collectors.joining(", ")) // une stringsstream.forEach(Consumer) // executa ação para cada elementostream.count() // conta elementosstream.findFirst() // retorna Optional com primeirostream.anyMatch(Predicate) // true se algum satisfazstream.allMatch(Predicate) // true se todos satisfazemstream.noneMatch(Predicate) // true se nenhum satisfazstream.min(Comparator) // retorna Optional com menorstream.max(Comparator) // retorna Optional com maiorstream.reduce(identity, BinaryOperator) // reduz a um único valorExemplos práticos
Seção intitulada “Exemplos práticos”List<String> nomes = List.of("Ana", "Bruno", "Carlos", "Diana", "Eduardo");
// Nomes com mais de 4 letras, em maiúsculas, ordenadosList<String> resultado = nomes.stream() .filter(n -> n.length() > 4) .map(String::toUpperCase) .sorted() .collect(Collectors.toList());// [BRUNO, CARLOS, DIANA, EDUARDO]
// Soma de todos os tamanhosint totalLetras = nomes.stream() .mapToInt(String::length) .sum();
// Agrupar por tamanhoMap<Integer, List<String>> porTamanho = nomes.stream() .collect(Collectors.groupingBy(String::length));Streams de tipos primitivos
Seção intitulada “Streams de tipos primitivos”IntStream, LongStream e DoubleStream evitam boxing/unboxing:
IntStream.range(1, 6).forEach(System.out::println); // 1 a 5IntStream.rangeClosed(1, 5).sum(); // 15
int[] numeros = {1, 2, 3, 4, 5};Arrays.stream(numeros).average().ifPresent(System.out::println); // 3.0Optional
Seção intitulada “Optional”Optional<T> é um container que pode ou não conter um valor. Resolve o problema de retornar null de métodos:
Optional<String> nome = Optional.of("João");Optional<String> vazio = Optional.empty();Optional<String> talvez = Optional.ofNullable(null); // empty
// Verificar e usarif (nome.isPresent()) { System.out.println(nome.get());}
// Forma mais elegantenome.ifPresent(System.out::println);
// Valor padrão se vazioString resultado = vazio.orElse("Anônimo");
// Lançar exceção se vazioString valor = nome.orElseThrow(() -> new RuntimeException("Sem nome"));
// Transformar se presenteOptional<Integer> tamanho = nome.map(String::length); // Optional[4]Uso típico - busca que pode não encontrar resultado:
public Optional<Usuario> buscarPorEmail(String email) { return usuarios.stream() .filter(u -> u.getEmail().equals(email)) .findFirst();}
// UsobuscarPorEmail("ana@exemplo.com") .ifPresentOrElse( u -> System.out.println("Encontrado: " + u.getNome()), () -> System.out.println("Usuário não encontrado") );Java Time API
Seção intitulada “Java Time API”A API de data/hora do Java 8 (pacote java.time) substituiu os problemáticos Date e Calendar.
Tipos principais
Seção intitulada “Tipos principais”import java.time.*;
LocalDate hoje = LocalDate.now(); // só data: 2026-04-26LocalTime agora = LocalTime.now(); // só hora: 14:30:00LocalDateTime dataHora = LocalDateTime.now(); // data e hora
ZonedDateTime comFuso = ZonedDateTime.now(ZoneId.of("America/Sao_Paulo"));
Instant instant = Instant.now(); // timestamp UnixCriação e manipulação
Seção intitulada “Criação e manipulação”LocalDate natal = LocalDate.of(2026, 12, 25);LocalDate amanha = hoje.plusDays(1);LocalDate semanaPassada = hoje.minusWeeks(1);
// Comparaçãoboolean eAntes = natal.isBefore(LocalDate.of(2027, 1, 1));
// Diferença entre datasPeriod periodo = Period.between(hoje, natal);System.out.println(periodo.getMonths() + " meses e " + periodo.getDays() + " dias");
// FormataçãoDateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd/MM/yyyy");String formatado = hoje.format(fmt); // "26/04/2026"LocalDate parseado = LocalDate.parse("25/12/2026", fmt);Text Blocks (Java 15+)
Seção intitulada “Text Blocks (Java 15+)”Text blocks simplificam strings multilinha, eliminando escape de aspas e concatenações:
// AntesString json = "{\n" + " \"nome\": \"Ana\",\n" + " \"idade\": 28\n" + "}";
// Com text blockString json = """ { "nome": "Ana", "idade": 28 } """;A indentação comum é removida automaticamente com base na posição do """ de fechamento.
String html = """ <html> <body> <p>Olá, Mundo!</p> </body> </html> """;Records (Java 16+)
Seção intitulada “Records (Java 16+)”Records são classes imutáveis e concisas para transportar dados. O compilador gera automaticamente construtor, getters, equals(), hashCode() e toString():
// Antes - muito boilerplatepublic class Ponto { private final int x; private final int y;
public Ponto(int x, int y) { this.x = x; this.y = y; }
public int x() { return x; } public int y() { return y; }
@Override public boolean equals(Object o) { ... } @Override public int hashCode() { ... } @Override public String toString() { ... }}
// Com record - equivalente ao código acimapublic record Ponto(int x, int y) {}Ponto p = new Ponto(3, 7);System.out.println(p.x()); // 3System.out.println(p); // Ponto[x=3, y=7]
// Validação no construtor compactopublic record Temperatura(double celsius) { public Temperatura { if (celsius < -273.15) { throw new IllegalArgumentException("Temperatura abaixo do zero absoluto"); } }}Records são ideais para DTOs, respostas de API e Value Objects.
Sealed Classes (Java 17+)
Seção intitulada “Sealed Classes (Java 17+)”Sealed classes restringem quais classes podem estender ou implementar um tipo, tornando a hierarquia explícita e fechada:
public sealed class Forma permits Circulo, Retangulo, Triangulo {}
public final class Circulo extends Forma { private final double raio; public Circulo(double raio) { this.raio = raio; } public double raio() { return raio; }}
public final class Retangulo extends Forma { private final double largura, altura; public Retangulo(double largura, double altura) { this.largura = largura; this.altura = altura; }}
public non-sealed class Triangulo extends Forma {} // permite extensão livreO compilador sabe que só existem esses subtipos, então switch exhaustivo não precisa de default:
double area = switch (forma) { case Circulo c -> Math.PI * c.raio() * c.raio(); case Retangulo r -> r.largura() * r.altura(); case Triangulo t -> calcularAreaTriangulo(t);};Pattern Matching para switch (Java 21+)
Seção intitulada “Pattern Matching para switch (Java 21+)”Combina type checking, cast e condições em uma única expressão:
Object obj = "Olá, Java!";
String resultado = switch (obj) { case Integer i -> "Inteiro: " + i; case String s when s.length() > 5 -> "String longa: " + s; case String s -> "String curta: " + s; case null -> "Nulo"; default -> "Outro: " + obj;};Virtual Threads (Java 21+)
Seção intitulada “Virtual Threads (Java 21+)”Virtual threads (Project Loom) são threads leves gerenciadas pela JVM, não pelo SO. Permitem criar milhares de threads sem o overhead das threads do sistema:
// Thread tradicional (1:1 com thread do SO)Thread t1 = new Thread(() -> System.out.println("Thread tradicional"));t1.start();
// Virtual thread - muito mais leveThread vt = Thread.ofVirtual().start(() -> System.out.println("Virtual thread"));
// Criar muitas virtual threads sem problematry (var executor = Executors.newVirtualThreadPerTaskExecutor()) { for (int i = 0; i < 10_000; i++) { executor.submit(() -> { Thread.sleep(Duration.ofMillis(100)); return "ok"; }); }}Virtual threads são ideais para operações de I/O (chamadas de API, banco de dados), onde a thread fica bloqueada esperando resposta. Com threads tradicionais, isso desperdiça recursos; com virtual threads, a JVM reutiliza o carrier thread enquanto espera.
Concurrency API
Seção intitulada “Concurrency API”Para além de virtual threads, o pacote java.util.concurrent oferece ferramentas de alto nível:
import java.util.concurrent.*;
// Pool de threadsExecutorService executor = Executors.newFixedThreadPool(4);
// Future: resultado assíncronoFuture<Integer> futuro = executor.submit(() -> { Thread.sleep(1000); return 42;});
System.out.println("Fazendo outra coisa...");int resultado = futuro.get(); // bloqueia até ter resultado
executor.shutdown();// CompletableFuture - mais flexívelCompletableFuture<String> cf = CompletableFuture .supplyAsync(() -> buscarDados()) .thenApply(dados -> processar(dados)) .thenApply(String::toUpperCase);
cf.thenAccept(System.out::println);