Especialização em Sistemas com Java 21 LTS • Módulo de Herança e Contratos


🗺️ Mapa Conceitual do Tópico

flowchart TD
    subgraph LEGADO["java.io Clássico (Bloqueante / Prone to Bugs)"]
        A["java.io.File (Retorna false sem motivo)"]
        B["FileInputStream / FileOutputStream (Bytes Crus)"]
        C["BufferedReader / BufferedWriter (Buffers em Charset)"]
        A --> B & C
    end

    subgraph NIO2["Java NIO.2 (java.nio.file - Moderno & Robusto)"]
        D["Path (Abstração Imutável de Caminhos)"]
        E["Files (Operações Estáticas Atômicas)"]
        F["Files.lines() (Streams Funcionais Lazy)"]
        G["StandardCopyOption.ATOMIC_MOVE"]
        D --> E
        E --> F & G
    end

    subgraph DESEMPENHO["Estratégias de Manipulação de Dados"]
        H["Leitura Pequena: Files.readString() / writeString()"]
        I["Arquivos Gigantes (Giga/Tera): Files.lines() com Buffer"]
        J["Binários / Mídia: Channels e ByteBuffers"]
    end

    NIO2 --> DESEMPENHO

    style A fill:#ffebee,stroke:#f44336,stroke-width:2px
    style D fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
    style E fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
    style F fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
    style I fill:#fff3e0,stroke:#ff9800,stroke-width:2px

📁 1. A Evolução do I/O: De java.io.File para o Moderno NIO.2

O pacote clássico java.io.File foi desenhado no Java 1.0 e sofre de severas restrições arquiteturais:

  1. Métodos como file.delete() ou file.createNewFile() retornam simples valores booleanos (false) em caso de erro, sem informar se a falha decorreu de permissões negadas, arquivo inexistente ou disco cheio.
  2. Não possui suporte a atributos nativos de sistemas de arquivos modernos (permissões POSIX, links simbólicos, notificações assíncronas do kernel).

Com a introdução do Java NIO.2 (JSR-203) a partir do Java 7 e refinado até o Java 21:

  • java.nio.file.Path: Interface imutável e type-safe que representa um local no sistema de arquivos de qualquer sistema operacional.
  • java.nio.file.Files: Classe utilitária recheada de operações atômicas que lançam exceções granulares e descritivas (NoSuchFileException, AccessDeniedException).

⚡ 2. Streams de Bytes vs Leitores de Caracteres (Char Streams)

A manipulação de dados em disco divide-se pelo tipo de dado transitado:

1. Streams de Bytes (InputStream e OutputStream)

Leem e escrevem bytes puros (0x00 a 0xFF). São obrigatórios para arquivos multimídia, planilhas binárias, executáveis ou arquivos criptografados.

try (InputStream is = Files.newInputStream(caminhoPdf)) { ... }

2. Leitores de Caracteres (Reader, Writer e Buffers)

Convertem bytes em caracteres aplicando tabelas de codificação (Charsets). No Java contemporâneo, sempre explicite o padrão StandardCharsets.UTF_8 para garantir portabilidade entre servidores Windows, Linux e macOS:

  • BufferedReader: Agrupa leituras em um buffer de memória (tamanho padrão de 8.192 caracteres), reduzindo drasticamente o número de system calls (interrupções no kernel do SO) para o disco.

🚀 3. Operações Modernas com Files no Java 11/17/21

A API moderna simplificou tarefas corriqueiras em poucas linhas de código elegante:

1. Leitura e Escrita Direta de Textos

Para arquivos de configuração ou payloads JSON/XML de pequeno ou médio porte:

Path caminho = Path.of("config", "app.json");
Files.writeString(caminho, jsonContent, StandardCharsets.UTF_8, StandardOpenOption.CREATE);
String conteudoLido = Files.readString(caminho, StandardCharsets.UTF_8);

2. Processamento Funcional Sob Demanda (Lazy Stream)

Para arquivos de log ou CSVs que ultrapassam a quantidade de memória RAM disponível na JVM:

// Abre o arquivo de forma sob demanda, fechando automaticamente com try-with-resources
try (Stream<String> linhas = Files.lines(Path.of("acessos.log"), StandardCharsets.UTF_8)) {
    long erros500 = linhas.filter(l -> l.contains("HTTP 500")).count();
    System.out.printf("Total de erros 500: %d%n", erros500);
}

📊 4. Matriz Comparativa: java.io.File vs java.nio.file.Path / Files

Recurso java.io.File (Legado) java.nio.file (NIO.2 Moderno)
Tratamento de Falhas Retorna false silencioso Lança exceções ricas (IOException específicas)
Operações Atômicas Não garantido Sim (StandardCopyOption.ATOMIC_MOVE)
Links Simbólicos Segue cegamente ou quebra Configuração explícita (LinkOption.NOFOLLOW_LINKS)
Eficiência em Memória Carrega listas inteiras em arrays Streams preguiçosas (Files.lines(), Files.walk())
Metadados do SO Básico (tamanho, timestamp) Acesso completo a atributos POSIX e ACLs

💻 5. Código de Demonstração Corporativo (Java 21 LTS)

package com.empresa.arquivos;

import java.io.BufferedWriter;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.stream.Stream;

/**
 * Pipeline corporativo de processamento de arquivos CSV com Java NIO.2.
 */
public class ProcessadorArquivosNio {

    public static void main(String[] args) {
        System.out.println("================================================================================");
        System.out.println("            PROCESSAMENTO EM LOTE COM JAVA NIO.2 (JAVA 21 LTS)                  ");
        System.out.println("================================================================================");

        Path diretorioTrabalho = Path.of("target", "lotes_financeiros");
        Path arquivoOrigem = diretorioTrabalho.resolve("remessa_20260928.csv");
        Path arquivoProcessado = diretorioTrabalho.resolve("remessa_processada.done");

        try {
            // 1. Criação atômica de diretórios se não existirem
            Files.createDirectories(diretorioTrabalho);

            // 2. Escrita simulada de arquivo de lote com BufferedWriter
            try (BufferedWriter writer = Files.newBufferedWriter(arquivoOrigem, StandardCharsets.UTF_8, 
                    StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
                writer.write("ID;CONTRATO;VALOR;STATUS\n");
                writer.write("1;CONT-9021;14500.50;QUITADO\n");
                writer.write("2;CONT-9022;3200.00;PENDENTE\n");
                writer.write("3;CONT-9023;8900.25;QUITADO\n");
            }
            System.out.printf("📁 Arquivo de lote criado com sucesso: [%s]%n", arquivoOrigem);

            // 3. Leitura preguiçosa com Files.lines() e cálculo com BigDecimal
            BigDecimal totalQuitado = BigDecimal.ZERO;
            int registrosProcessados = 0;

            try (Stream<String> streamLinhas = Files.lines(arquivoOrigem, StandardCharsets.UTF_8)) {
                for (String linha : (Iterable<String>) streamLinhas.skip(1)::iterator) {
                    String[] colunas = linha.split(";");
                    String status = colunas[3];
                    BigDecimal valor = new BigDecimal(colunas[2]);

                    if ("QUITADO".equalsIgnoreCase(status)) {
                        totalQuitado = totalQuitado.add(valor);
                    }
                    registrosProcessados++;
                }
            }

            System.out.println("--------------------------------------------------------------------------------");
            System.out.printf("Total de Registros Lidos : %d linhas%n", registrosProcessados);
            System.out.printf("Volume Total Quitado     : R$ %,10.2f%n", totalQuitado);

            // 4. Renomeação atômica simulando conclusão de processamento
            Files.move(arquivoOrigem, arquivoProcessado, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
            System.out.printf("✅ Lote promovido para estado final: [%s]%n", arquivoProcessado.getFileName());

        } catch (IOException e) {
            System.err.printf("🚨 Falha na operação de I/O: %s%n", e.getMessage());
        }

        System.out.println("================================================================================");
    }
}