Este projeto é uma demonstração prática e aprofundada da utilização de Java Generics para a construção de um Data Access Object (DAO) genérico e reutilizável.
O foco principal é ilustrar como os Generics, quando aplicados corretamente, permitem a criação de APIs robustas, flexíveis e seguras em tempo de compilação. Mais do que apenas uma implementação de CRUD, este projeto serve como um guia para o design de componentes de software que são tanto extensíveis quanto fáceis de manter.
O GenericDAO oferece um conjunto de operações de persistência de dados em memória, que podem ser facilmente adaptadas para outras formas de armazenamento (como bancos de dados). As principais funcionalidades incluem:
- Salvar uma entidade:
save(T domain) - Salvar múltiplas entidades:
saveBatch(T... domains)esaveAll(List<? extends T> items) - Buscar uma entidade:
find(Predicate<T> filter) - Buscar todas as entidades:
findAll() - Atualizar uma entidade:
update(ID id, T domain) - Deletar uma entidade:
delete(T domain) - Contar o número de entidades:
count()
Além disso, a interface Repository demonstra o uso de métodos estáticos e genéricos para operações auxiliares, como printIds e addIntegers.
Para utilizar o GenericDAO, você precisa seguir estes passos:
- Definir sua entidade de domínio, que deve estender
GenericDomain<ID>:
public class User extends GenericDomain<Long> {
private String name;
public User(Long id, String name) {
super(id);
this.name = name;
}
// Getters e Setters
}- Criar uma implementação de DAO que estenda
GenericDAO<ID, T>:
public class UserDAO extends GenericDAO<Long, User> {
// Implementação específica do UserDAO, se necessário
}- Utilizar o DAO em sua aplicação:
public class Main {
public static void main(String[] args) {
UserDAO userDAO = new UserDAO();
User user1 = new User(1L, "John Doe");
userDAO.save(user1);
System.out.println("Usuários salvos: " + userDAO.count());
}
}public abstract class GenericDAO<ID, T extends GenericDomain<ID>>Garante que:
- todo domínio possui um ID
- o DAO nunca trabalha com tipos inválidos
- existe consistência entre entidade e identificador
| Cenário | Wildcard |
|---|---|
| Só lê da coleção | ? extends T |
| Só escreve na coleção | ? super T |
| Lê e escreve | T |
Essa regra é aplicada conscientemente em todos os métodos do DAO.
Os testes não validam apenas comportamento, mas sim:
- contratos genéricos
- flexibilidade de tipos
- segurança da API
- intenção do design
Cada teste utiliza @DisplayName para funcionar como documentação executável.
@DisplayName("save(T) → Consumes and produces the same generic type")📌 Por quê?
O método consome e produz T, portanto não utiliza wildcard.
✔ Demonstra quando NÃO usar extends ou super.
@DisplayName("saveBatch(T...) → Saves multiple elements keeping type safety")📌 Demonstra:
- uso correto de varargs genérico
- preservação total do tipo
T
@DisplayName("saveAll(Collection<? extends T>) → Reads from a producer collection (covariance)")📌 Ponto mais importante do projeto
Mesmo utilizando addAll, o método:
- ❌ não escreve em
items - ✅ apenas lê dela
Logo:
items→ PRODUZTdb→ CONSOMET
✔ Uso correto de covariância
@ParameterizedTest
@ValueSource(ints = {1, 3, 5})📌 Demonstra que:
- o método funciona para qualquer tamanho de coleção
- o contrato genérico é independente da implementação concreta
@DisplayName("find(Predicate<T>) → Finds elements using a generic predicate")📌 Demonstra:
- integração de Generics com programação funcional
- segurança de tipo mesmo com lambdas
@DisplayName("findAll() → Returns an immutable snapshot of internal storage")📌 Demonstra:
- encapsulamento
- retorno seguro (
List.copyOf) - proteção contra efeitos colaterais
@DisplayName("update(ID, T) → Preserves generic ID consistency across updates")📌 Demonstra:
- uso correto de ID genérico
- manutenção do contrato entre entidade e identificador
public static <T extends GenericDomain<?>> void printIds(List<T> items)📌 Demonstra:
- generics em métodos estáticos
- independência do tipo de ID
Testado com diferentes coleções sem perda de segurança.
@DisplayName("addIntegers(List<? super Integer>) → Consumes Integers using contravariance")📌 Demonstra escrita segura em:
List<Integer>List<Number>List<Object>
✔ Exemplo real, não teórico, de contravariância.
@MethodSource("superTypeLists")📌 Prova que:
- o método aceita múltiplos supertypes
- o contrato genérico é estável e extensível
Java Generics não são sintaxe.
São contrato de API.
Se seus testes não validam seus Generics,
eles não estão protegendo sua arquitetura.
Elvis Assis
Java | Spring Boot | Arquitetura | Clean Code | Generics Avançado