Diferenças entre Classes, Interfaces e Records em Java
Quando estamos desenvolvendo sistemas em Java, é fundamental entender como organizar o código para manter a aplicação clara, segura e fácil de manter. Neste artigo, vamos explicar de forma simples e prática o papel das classes, interfaces e records, e como eles ajudam no desenvolvimento, principalmente na separação de responsabilidades.
O que é uma Classe em Java?
Uma classe é a estrutura que representa um objeto real dentro do sistema. Ela funciona como um molde que define os atributos (os dados que o objeto armazena) e os métodos (as ações que o objeto pode executar). As classes são a base para modelar os dados do seu domínio, refletindo as informações que serão armazenadas em um banco de dados e manipuladas pela aplicação.
Por exemplo, a classe Produto pode representar um item com nome, preço e também dados internos que não devem ser expostos publicamente.
O que é uma Interface?
Uma interface funciona como um contrato que define o que uma classe deve fazer, mas sem detalhar como ela fará. Ela estabelece um conjunto de métodos que qualquer classe que a implemente deve obrigatoriamente fornecer. Isso garante consistência e flexibilidade no sistema, permitindo que diferentes classes ofereçam implementações variadas para o mesmo contrato.
Com isso, diferentes classes podem implementar essa interface de maneiras variadas, mantendo a flexibilidade e organização.
E o que é um Record?
O Record foi introduzido nas versões mais recentes do Java para facilitar a criação de objetos que são simples e imutáveis. Seu principal objetivo é servir como um transportador de dados entre as diferentes camadas de um sistema. Ele é a ferramenta perfeita para criar o que chamamos de DTO (Data Transfer Object). Um DTO é um objeto que carrega apenas os dados essenciais necessários para uma comunicação ou exibição , sem o risco de expor detalhes internos ou informações sensíveis
Por que usar DTOs (Records) em vez de expor as Classes diretamente?
Imagine que a sua classe principal de Produto contém informações internas que não devem ser vistas pelo usuário, como detalhes de fornecedores ou outros dados sigilosos. Se você enviar o objeto Produto completo diretamente para uma API, pode acabar expondo esses dados que não deveria.
Com um DTO (usando record), você escolhe exatamente quais campos do seu objeto serão expostos. Ao criar o ProdutoDTO apenas com id, nome e preco, você garante que nenhuma informação interna seja vazada. Assim, sua aplicação se torna mais segura, organizada e você protege os dados sensíveis.
Exemplo prático resumido
- Produto é a classe com todos os dados, inclusive sensíveis.
/**
* Classe Produto - representa a entidade real com dados completos,
* incluindo informações que não queremos expor fora da aplicação.
*/
public class Produto {
private Long id;
private String nome;
private double preco;
private String fornecedorInternoSigiloso;
public Produto(Long id, String nome, double preco, String fornecedorInternoSigiloso) {
this.id = id;
this.nome = nome;
this.preco = preco;
this.fornecedorInternoSigiloso = fornecedorInternoSigiloso;
}
// Getters
public Long getId() { return id; }
public String getNome() { return nome; }
public double getPreco() { return preco; }
public String getFornecedorInternoSigiloso() { return fornecedorInternoSigiloso; }
}
- ProdutoDTO é o record que só carrega os dados que queremos expor.
/**
* ProdutoDTO - Data Transfer Object (DTO)
* Representa os dados essenciais do Produto para serem usados em outras camadas
* (como na interface do usuário, APIs, etc), omitindo dados sensíveis.
*/
public record ProdutoDTO(Long id, String nome, double preco) {}
- ProdutoService é a interface que define as operações possíveis.
/**
* Interface ProdutoService
* Define o que qualquer serviço de produto deve implementar.
* Aqui está a assinatura dos métodos públicos para cadastrar e listar produtos.
*/
public interface ProdutoService {
void cadastrarProduto(ProdutoDTO produto);
void listarProdutos();
}
- ProdutoServiceImpl é a classe que implementa essa interface e faz a conversão entre Produto e ProdutoDTO.
import java.util.ArrayList;
import java.util.List;
/**
* Implementação concreta do ProdutoService.
* Aqui guardamos os produtos internamente (simulando um banco)
* e fazemos a conversão entre ProdutoDTO e Produto.
*/
public class ProdutoServiceImpl implements ProdutoService {
private List<Produto> produtos = new ArrayList<>();
@Override
public void cadastrarProduto(ProdutoDTO dto) {
System.out.println("Convertendo ProdutoDTO para Produto (entidade interna)...");
Produto produto = new Produto(dto.id(), dto.nome(), dto.preco(), "Fornecedor interno");
produtos.add(produto);
System.out.println("Produto cadastrado com sucesso: " + dto);
System.out.println("------------------------------------------");
}
@Override
public void listarProdutos() {
System.out.println("\nListando produtos cadastrados (convertendo Produto para ProdutoDTO):");
for (Produto p : produtos) {
ProdutoDTO dto = new ProdutoDTO(p.getId(), p.getNome(), p.getPreco());
System.out.println(dto);
}
System.out.println("------------------------------------------");
}
}
- Aqui está um exempo de main para testar:
/**
* Classe Main para rodar o exemplo e mostrar o uso da interface, classe e record.
*/
public class Main {
public static void main(String[] args) {
System.out.println("=== Criando serviço de produtos (ProdutoService) ===");
ProdutoService service = new ProdutoServiceImpl();
System.out.println("\n=== Cadastrando produtos (usando ProdutoDTO) ===");
service.cadastrarProduto(new ProdutoDTO(1L, "Notebook", 3500.00));
service.cadastrarProduto(new ProdutoDTO(2L, "Mouse", 150.00));
service.cadastrarProduto(new ProdutoDTO(3L, "Teclado", 250.00));
System.out.println("\n=== Listando todos os produtos cadastrados ===");
service.listarProdutos();
System.out.println("\n=== Fim do programa ===");
}
}
- Tabela resumida: