Singular
@Singular
La anotación @Singular se usa junto con @Builder o @SuperBuilder para construir colecciones (listas, conjuntos, mapas) de forma cómoda y segura.
- Funciona solo dentro de
@Buildero@SuperBuilder.
✅ ¿Para qué sirve?
Permite agregar elementos uno a uno al builder, en lugar de pasar una lista completa.
import lombok.experimental.SuperBuilder;
import lombok.Singular;
import java.util.List;
@SuperBuilder
class Persona {
String nombre;
@Singular
List<String> hobbies;
}
Persona persona = Persona.builder()
.nombre("Laura")
.hobby("Leer")
.hobby("Viajar")
.build();
Sin @Singular tendrias que hacer esto
Persona persona = Persona.builder()
.nombre("Laura")
.hobbies(Arrays.asList("Leer", "Viajar"))
.build();
¿Qué genera Lombok con @Singular?
Al poner @Singular List<String> hobbies, Lombok genera automáticamente:
hobby(String hobby)→ Agrega un solo hobby.hobbies(Collection<String> hobbies)→ Agrega varios.clearHobbies()→ Limpia la lista.
Consideraciones
-
Lombok intenta convertir el nombre de la colección al singular automáticamente (ej.
hobbies→hobby). Esto funciona bien con palabras en inglés comunes.Si tu colección no es fácil de convertir, puedes indicar manualmente el singular:
@Singular("pasatiempo")
List<String> pasatiempos;
.builder()
.pasatiempo("Leer")
.pasatiempo("Cantar");
- Soporta las siguientes collecciones
java.util:Iterable,Collection, andList(backed by a compacted unmodifiableArrayListin the general case).Set,SortedSet, andNavigableSet(backed by a smartly sized unmodifiableHashSetorTreeSetin the general case).Map,SortedMap, andNavigableMap(backed by a smartly sized unmodifiableHashMaporTreeMapin the general case).
- Guava’s
com.google.common.collect:ImmutableCollectionandImmutableList(backed by the builder feature ofImmutableList).ImmutableSetandImmutableSortedSet(backed by the builder feature of those types).ImmutableMap,ImmutableBiMap, andImmutableSortedMap(backed by the builder feature of those types).ImmutableTable(backed by the builder feature ofImmutableTable).
Personalización
ignoreNullCollections = true
@Singular(ignoreNullCollections = true)
List<String> hobbies;
Comportamiento por defecto (sin ignoreNullCollections)
Persona.builder()
.hobbies(null) // ❌ Esto lanza NullPointerException
.build();
con ignoreNullCollections = true
En su lugar: Ignora el null , no agrega nada, sigue funcionando normalmente
Persona.builder()
.hobbies(null) // ✅ No hace nada, no lanza excepción
.build();
Singular with Jackson
- Lombok genera automáticamente un builder para tu clase cuando usas
@Buildero@SuperBuilder. - Jackson, que se usa para serializar/deserializar JSON, necesita a veces configuraciones especiales para que el builder funcione bien con sus reglas.
- Para que Jackson use el builder generado por Lombok durante la deserialización, debes indicarle explícitamente que use el builder con la anotación
@JsonDeserialize(builder=...).
@Value @Builder
@JsonDeserialize(builder = JacksonExample.JacksonExampleBuilder.class)
public class JacksonExample {
@Singular(nullBehavior = NullCollectionBehavior.IGNORE) private List<Foo> foos;
@JsonPOJOBuilder(withPrefix = "")
public static class JacksonExampleBuilder implements JacksonExampleBuilderMeta {
}
private interface JacksonExampleBuilderMeta {
@JsonDeserialize(contentAs = FooImpl.class)
JacksonExampleBuilder foos(List<? extends Foo> foos);
}
}
1. @JsonDeserialize(builder = JacksonExample.JacksonExampleBuilder.class)
Le dices a Jackson:
Para deserializar esta clase desde JSON, usa este builder que está dentro de la clase.
2. Definir el builder manualmente
- Aunque Lombok genera el builder automáticamente, aquí defines explícitamente la clase
JacksonExampleBuilder. - Esto te permite añadir anotaciones propias de Jackson o añadir métodos personalizados.
3. @JsonPOJOBuilder(withPrefix = "")
- Indica que los métodos del builder no tienen prefijo (por defecto Jackson busca métodos con prefijo
withoset). - Por ejemplo, el método
foos(...)en el builder será reconocido correctamente sin necesidad de un prefijo comowithFoos.
4. Interfaz JacksonExampleBuilderMeta
- Aquí defines métodos que quieres personalizar, como el método
foos(List<? extends Foo> foos)para el builder. - Anotas este método con
@JsonDeserialize(contentAs = FooImpl.class)para indicarle a Jackson que los elementos de la listafoosdeben deserializarse comoFooImpl, que es una implementación concreta de la interfazFoo.