fbpx

Lombok en JAVA

Project Lombok es una librería JAVA que se conecta automáticamente a su editor y herramientas de construcción, condimentando su JAVA.
No vuelvas a escribir otro método getter o equals, con una anotación tu clase tiene un constructor completo, automatiza tus variables de registro, y mucho más.

Vídeo explicativo

Anotaciones de Lombok

@NonNull

Puedes utilizar @NonNull en un componente de un registro, o en un parámetro de un método o constructor. Esto hará que Lombok genere una sentencia null-check por ti. Es decir, gracias a Lombok podrás abstraerte de verificar si una variable es nula y lanzar una excepción NullPointerException.

Con Lombok

import lombok.NonNull;

public class NonNullEjemplo {
  private String nombre;
  
  public NonNullExample(@NonNull Persona persona) {
    this.name = persona.getNombre();
  }
}

Sin Lombok

import javax.annotation.Nonnull;

public class NonNullEjemplo {
  private String nombre;
  
  public NonNullExample(@NonNull Persona persona) {
    if (persona == null) {
      throw new NullPointerException("persona esta anotada con nonnull pero es null");
    }
    this.name = persona.getNombre();
  }
}

@Cleanup

Puede utilizar @Cleanup para asegurarse de que un recurso determinado se limpia automáticamente antes de que la ruta de ejecución del código salga de su ámbito actual. Para ello, anote cualquier declaración de variable local con la anotación @Cleanup de la siguiente manera:
@Cleanup InputStream in = new FileInputStream("algo/archivo");
Como resultado, al final del ámbito en el que se encuentra, se llama a in.close(). Se garantiza la ejecución de esta llamada mediante una construcción try/finally. Mira el siguiente ejemplo para ver cómo funciona.

Con Lombok

import lombok.Cleanup;
import java.io.*;

public class CleanupExample {
  public static void main(String[] args) throws IOException {
    @Cleanup InputStream in = new FileInputStream(args[0]);
    @Cleanup OutputStream out = new FileOutputStream(args[1]);
    byte[] b = new byte[10000];
    while (true) {
      int r = in.read(b);
      if (r == -1) break;
      out.write(b, 0, r);
    }
  }
}

Sin Lombok

import java.io.*;

public class CleanupExample {
  public static void main(String[] args) throws IOException {
    InputStream in = new FileInputStream(args[0]);
    try {
      OutputStream out = new FileOutputStream(args[1]);
      try {
        byte[] b = new byte[10000];
        while (true) {
          int r = in.read(b);
          if (r == -1) break;
          out.write(b, 0, r);
        }
      } finally {
        if (out != null) {
          out.close();
        }
      }
    } finally {
      if (in != null) {
        in.close();
      }
    }
  }
}

@Getter/@Setter

Puedes anotar cualquier campo con @Getter y/o @Setter, para dejar que Lombok genere automáticamente el getter/setter por defecto.
Un getter por defecto simplemente devuelve el campo, y se llama getFoo si el campo se llama foo (o isFoo si el tipo del campo es booleano). Un setter por defecto se llama setFoo si el campo se llama foo, devuelve void, y toma 1 parámetro del mismo tipo que el campo. Simplemente establece el campo a este valor.

El método getter/setter generado será público a menos que especifiques explícitamente un AccessLevel, como se muestra en el siguiente ejemplo. Los niveles de acceso legales son PUBLIC, PROTECTED, PACKAGE y PRIVATE.

También puede poner una anotación @Getter y/o @Setter en una clase. En ese caso, es como si anotaras todos los campos no estáticos de esa clase con la anotación.

Siempre puedes desactivar manualmente la generación de getter/setter para cualquier campo utilizando el nivel de acceso especial AccessLevel.NONE. Esto te permite anular el comportamiento de una anotación @Getter, @Setter o @Data en una clase.

Con Lombok

import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;

public class GetterSetterEjemplo {

  @Getter @Setter private int edad = 10;
  
  @Setter(AccessLevel.PROTECTED) private String nombre;
   // Más código...
}

Sin Lombok

public class GetterSetterExample {

  private int edad = 10;

  private String nombre;
  
  public int getEdad() {
    return edad;
  }
 
  public void setEdad(int edad) {
    this.edad = edad;
  }

  protected void setNombre(String nombre) {
    this.nombre = nombre;
  }
}

@ToString

Anotar una clase con @ToString hará que Lombok genere una implementación del método toString(). Puede utilizar las opciones de configuración para especificar si los nombres de campo deben incluirse, pero por lo demás el formato es fijo: el nombre de la clase seguido de paréntesis que contienen campos separados por comas, por ejemplo, MyClass(foo=123, bar=234).

Con Lombok

import lombok.ToString;

@ToString
public class ToStringEjemplo {
  private static final int STATIC_VAR = 10;
  private String nombre;
  private Figura figura = new Cuadrado(5, 10);
  private String[] tags;
  @ToString.Exclude private int id;
  
  public String getNombre() {
    return this.nombre;
  }
  
  @ToString(callSuper=true, includeFieldNames=true)
  public static class Cuadrado extends Figura {
    private final int anchura, altura;
    
    public Square(int anchura, int altura) {
      this.anchura = anchura;
      this.altura = altura;
    }
  }
}

Sin Lombok

import java.util.Arrays;

public class ToStringEjemplo {
  private static final int STATIC_VAR = 10;
  private String nombre;
  private Figura figura = new Cuadrado(5, 10);
  private String[] tags;
  private int id;
  
  public String getNombre() {
    return this.nombre;
  }
  
  public static class Cuadrado extends Shape {
    private final int anchura, altura;
    
    public Square(int anchura, int altura) {
      this.anchura = anchura;
      this.altura = altura;
    }
    
    @Override public String toString() {
      return "Square(super=" + super.toString() + ", anchura=" + this.anchura + ", altura=" + this.altura + ")";
    }
  }
  
  @Override public String toString() {
    return "ToStringEjemplo(" + this.getNnombre() + ", " + this.figura + ", " + Arrays.deepToString(this.tags) + ")";
  }
}

@EqualsAndHashCode

Cualquier definición de clase puede ser anotada con @EqualsAndHashCode para que lombok genere implementaciones de los métodos equals(Object other) y hashCode(). Por defecto, utilizará todos los campos no estáticos y no transitorios, pero se puede modificar qué campos se utilizan (e incluso especificar que se utilice la salida de varios métodos) marcando los miembros de la clase con @EqualsAndHashCode.Include o @EqualsAndHashCode.Exclude. También puede especificar exactamente qué campos o métodos desea que se utilicen marcándolos con @EqualsAndHashCode.Include y utilizando @EqualsAndHashCode(onlyExplicitlyIncluded = true).

Si se aplica @EqualsAndHashCode a una clase que extiende a otra, esta función se complica un poco. Normalmente, auto-generar un método equals y hashCode para tales clases es una mala idea, ya que la superclase también define campos, que también necesitan código equals/hashCode pero este código no será generado. Estableciendo callSuper a true, puedes incluir los métodos equals y hashCode de tu superclase en los métodos generados. Para hashCode, el resultado de super.hashCode() se incluye en el algoritmo hash, y para equals, el método generado devolverá false si la implementación super piensa que no es igual al objeto pasado. Tenga en cuenta que no todas las implementaciones de equals manejan esta situación correctamente. Sin embargo, las implementaciones de equals generadas por lombok manejan esta situación correctamente, por lo que puede llamar a su superclase equals con seguridad si también tiene un método equals generado por lombok. Si tiene una superclase explícita, está obligado a proporcionar algún valor para callSuper para reconocer que lo ha considerado; si no lo hace, recibirá una advertencia.

Con Lombok

import lombok.EqualsAndHashCode;

@EqualsAndHashCode
public class EqualsAndHashCodeExample {
  private transient int transientVar = 10;
  private String name;
  private double score;
  @EqualsAndHashCode.Exclude private Shape shape = new Square(5, 10);
  private String[] tags;
  @EqualsAndHashCode.Exclude private int id;
  
  public String getName() {
    return this.name;
  }
  
  @EqualsAndHashCode(callSuper=true)
  public static class Square extends Shape {
    private final int width, height;
    
    public Square(int width, int height) {
      this.width = width;
      this.height = height;
    }
  }
}

Sin Lombok

import java.util.Arrays;

public class EqualsAndHashCodeExample {
  private transient int transientVar = 10;
  private String name;
  private double score;
  private Shape shape = new Square(5, 10);
  private String[] tags;
  private int id;
  
  public String getName() {
    return this.name;
  }
  
  @Override public boolean equals(Object o) {
    if (o == this) return true;
    if (!(o instanceof EqualsAndHashCodeExample)) return false;
    EqualsAndHashCodeExample other = (EqualsAndHashCodeExample) o;
    if (!other.canEqual((Object)this)) return false;
    if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false;
    if (Double.compare(this.score, other.score) != 0) return false;
    if (!Arrays.deepEquals(this.tags, other.tags)) return false;
    return true;
  }
  
  @Override public int hashCode() {
    final int PRIME = 59;
    int result = 1;
    final long temp1 = Double.doubleToLongBits(this.score);
    result = (result*PRIME) + (this.name == null ? 43 : this.name.hashCode());
    result = (result*PRIME) + (int)(temp1 ^ (temp1 >>> 32));
    result = (result*PRIME) + Arrays.deepHashCode(this.tags);
    return result;
  }
  
  protected boolean canEqual(Object other) {
    return other instanceof EqualsAndHashCodeExample;
  }
  
  public static class Square extends Shape {
    private final int width, height;
    
    public Square(int width, int height) {
      this.width = width;
      this.height = height;
    }
    
    @Override public boolean equals(Object o) {
      if (o == this) return true;
      if (!(o instanceof Square)) return false;
      Square other = (Square) o;
      if (!other.canEqual((Object)this)) return false;
      if (!super.equals(o)) return false;
      if (this.width != other.width) return false;
      if (this.height != other.height) return false;
      return true;
    }
    
    @Override public int hashCode() {
      final int PRIME = 59;
      int result = 1;
      result = (result*PRIME) + super.hashCode();
      result = (result*PRIME) + this.width;
      result = (result*PRIME) + this.height;
      return result;
    }
    
    protected boolean canEqual(Object other) {
      return other instanceof Square;
    }
  }
}

@NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor

Este conjunto de 3 anotaciones generan un constructor que aceptará 1 parámetro para ciertos campos, y simplemente asigna este parámetro al campo.

@NoArgsConstructor generará un constructor sin parámetros. Si esto no es posible (debido a campos finales), se producirá un error de compilación, a menos que se utilice @NoArgsConstructor(force = true), entonces todos los campos finales se inicializan con 0 / false / null. Para los campos con restricciones, como los campos @NonNull, no se genera ninguna comprobación, así que tenga en cuenta que estas restricciones generalmente no se cumplirán hasta que esos campos se inicialicen correctamente más tarde. Ciertas construcciones java, como hibernate y la interfaz de proveedor de servicios, requieren un constructor no-args. Esta anotación es útil principalmente en combinación con @Data o una de las otras anotaciones de generación de constructores.

@RequiredArgsConstructor genera un constructor con 1 parámetro para cada campo que requiera un tratamiento especial. Todos los campos finales no inicializados reciben un parámetro, así como cualquier campo marcado como @NonNull que no esté inicializado donde se declara. Para aquellos campos marcados con @NonNull, también se genera una comprobación null explícita. El constructor lanzará una NullPointerException si alguno de los parámetros destinados a los campos marcados con @NonNull contienen null. El orden de los parámetros coincide con el orden en que aparecen los campos en su clase.

@AllArgsConstructor genera un constructor con 1 parámetro para cada campo de su clase. Los campos marcados con @NonNull dan lugar a comprobaciones de nulos en esos parámetros.

Cada una de estas anotaciones permite una forma alternativa, donde el constructor generado es siempre privado, y se genera un método de fábrica estático adicional que envuelve al constructor privado. Este modo se activa proporcionando el valor staticName para la anotación, de esta forma: @RequiredArgsConstructor(staticName="of"). Este método de fábrica estático inferirá genéricos, a diferencia de un constructor normal. Esto significa que los usuarios de la API escribirán MapEntry.of("foo", 5) en lugar del método mucho más largo new MapEntry("foo", 5).

Con Lombok

import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.NonNull;

@RequiredArgsConstructor(staticName = "of")
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public class ConstructorExample<T> {
  private int x, y;
  @NonNull private T description;
  
  @NoArgsConstructor
  public static class NoArgsExample {
    @NonNull private String field;
  }
}

Sin Lombok

public class ConstructorExample<T> {
  private int x, y;
  @NonNull private T description;
  
  private ConstructorExample(T description) {
    if (description == null) throw new NullPointerException("description");
    this.description = description;
  }
  
  public static <T> ConstructorExample<T> of(T description) {
    return new ConstructorExample<T>(description);
  }
  
  @java.beans.ConstructorProperties({"x", "y", "description"})
  protected ConstructorExample(int x, int y, T description) {
    if (description == null) throw new NullPointerException("description");
    this.x = x;
    this.y = y;
    this.description = description;
  }
  
  public static class NoArgsExample {
    @NonNull private String field;
    
    public NoArgsExample() {
    }
  }
}

@Data

@Data es un cómodo atajo que reúne las características de @ToString, @EqualsAndHashCode, @Getter / @Setter y @RequiredArgsConstructor: En otras palabras, @Data genera todo el boilerplate que normalmente se asocia con POJOs simples (Plain Old Java Objects) y beans: getters para todos los campos, setters para todos los campos no finales, e implementaciones apropiadas de toString, equals y hashCode que involucran los campos de la clase, y un constructor que inicializa todos los campos finales, así como todos los campos no finales sin inicializador que han sido marcados con @NonNull, para asegurar que el campo nunca es nulo.

Con Lombok

import lombok.AccessLevel;
import lombok.Setter;
import lombok.Data;
import lombok.ToString;

@Data public class DataExample {
  private final String name;
  @Setter(AccessLevel.PACKAGE) private int age;
  private double score;
  private String[] tags;
  
  @ToString(includeFieldNames=true)
  @Data(staticConstructor="of")
  public static class Exercise<T> {
    private final String name;
    private final T value;
  }
}

Sin Lombok

import java.util.Arrays;

public class DataExample {
  private final String name;
  private int age;
  private double score;
  private String[] tags;
  
  public DataExample(String name) {
    this.name = name;
  }
  
  public String getName() {
    return this.name;
  }
  
  void setAge(int age) {
    this.age = age;
  }
  
  public int getAge() {
    return this.age;
  }
  
  public void setScore(double score) {
    this.score = score;
  }
  
  public double getScore() {
    return this.score;
  }
  
  public String[] getTags() {
    return this.tags;
  }
  
  public void setTags(String[] tags) {
    this.tags = tags;
  }
  
  @Override public String toString() {
    return "DataExample(" + this.getName() + ", " + this.getAge() + ", " + this.getScore() + ", " + Arrays.deepToString(this.getTags()) + ")";
  }
  
  protected boolean canEqual(Object other) {
    return other instanceof DataExample;
  }
  
  @Override public boolean equals(Object o) {
    if (o == this) return true;
    if (!(o instanceof DataExample)) return false;
    DataExample other = (DataExample) o;
    if (!other.canEqual((Object)this)) return false;
    if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false;
    if (this.getAge() != other.getAge()) return false;
    if (Double.compare(this.getScore(), other.getScore()) != 0) return false;
    if (!Arrays.deepEquals(this.getTags(), other.getTags())) return false;
    return true;
  }
  
  @Override public int hashCode() {
    final int PRIME = 59;
    int result = 1;
    final long temp1 = Double.doubleToLongBits(this.getScore());
    result = (result*PRIME) + (this.getName() == null ? 43 : this.getName().hashCode());
    result = (result*PRIME) + this.getAge();
    result = (result*PRIME) + (int)(temp1 ^ (temp1 >>> 32));
    result = (result*PRIME) + Arrays.deepHashCode(this.getTags());
    return result;
  }
  
  public static class Exercise<T> {
    private final String name;
    private final T value;
    
    private Exercise(String name, T value) {
      this.name = name;
      this.value = value;
    }
    
    public static <T> Exercise<T> of(String name, T value) {
      return new Exercise<T>(name, value);
    }
    
    public String getName() {
      return this.name;
    }
    
    public T getValue() {
      return this.value;
    }
    
    @Override public String toString() {
      return "Exercise(name=" + this.getName() + ", value=" + this.getValue() + ")";
    }
    
    protected boolean canEqual(Object other) {
      return other instanceof Exercise;
    }
    
    @Override public boolean equals(Object o) {
      if (o == this) return true;
      if (!(o instanceof Exercise)) return false;
      Exercise<?> other = (Exercise<?>) o;
      if (!other.canEqual((Object)this)) return false;
      if (this.getName() == null ? other.getValue() != null : !this.getName().equals(other.getName())) return false;
      if (this.getValue() == null ? other.getValue() != null : !this.getValue().equals(other.getValue())) return false;
      return true;
    }
    
    @Override public int hashCode() {
      final int PRIME = 59;
      int result = 1;
      result = (result*PRIME) + (this.getName() == null ? 43 : this.getName().hashCode());
      result = (result*PRIME) + (this.getValue() == null ? 43 : this.getValue().hashCode());
      return result;
    }
  }
}

@Value

@Value es la variante inmutable de @Data; todos los campos son privados y finales por defecto, y no se generan setters. La propia clase también se hace final por defecto, porque la inmutabilidad no es algo que se pueda forzar en una subclase. Al igual que @Data, también se generan los útiles métodos toString(), equals() y hashCode(), cada campo obtiene un método getter, y también se genera un constructor que cubre todos los argumentos (excepto los campos finales que se inicializan en la declaración del campo).

Con Lombok

import lombok.AccessLevel;
import lombok.experimental.NonFinal;
import lombok.experimental.Value;
import lombok.experimental.With;
import lombok.ToString;

@Value public class ValueExample {
  String name;
  @With(AccessLevel.PACKAGE) @NonFinal int age;
  double score;
  protected String[] tags;
  
  @ToString(includeFieldNames=true)
  @Value(staticConstructor="of")
  public static class Exercise<T> {
    String name;
    T value;
  }
}

Sin Lombok

import java.util.Arrays;

public final class ValueExample {
  private final String name;
  private int age;
  private final double score;
  protected final String[] tags;
  
  @java.beans.ConstructorProperties({"name", "age", "score", "tags"})
  public ValueExample(String name, int age, double score, String[] tags) {
    this.name = name;
    this.age = age;
    this.score = score;
    this.tags = tags;
  }
  
  public String getName() {
    return this.name;
  }
  
  public int getAge() {
    return this.age;
  }
  
  public double getScore() {
    return this.score;
  }
  
  public String[] getTags() {
    return this.tags;
  }
  
  @java.lang.Override
  public boolean equals(Object o) {
    if (o == this) return true;
    if (!(o instanceof ValueExample)) return false;
    final ValueExample other = (ValueExample)o;
    final Object this$name = this.getName();
    final Object other$name = other.getName();
    if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false;
    if (this.getAge() != other.getAge()) return false;
    if (Double.compare(this.getScore(), other.getScore()) != 0) return false;
    if (!Arrays.deepEquals(this.getTags(), other.getTags())) return false;
    return true;
  }
  
  @java.lang.Override
  public int hashCode() {
    final int PRIME = 59;
    int result = 1;
    final Object $name = this.getName();
    result = result * PRIME + ($name == null ? 43 : $name.hashCode());
    result = result * PRIME + this.getAge();
    final long $score = Double.doubleToLongBits(this.getScore());
    result = result * PRIME + (int)($score >>> 32 ^ $score);
    result = result * PRIME + Arrays.deepHashCode(this.getTags());
    return result;
  }
  
  @java.lang.Override
  public String toString() {
    return "ValueExample(name=" + getName() + ", age=" + getAge() + ", score=" + getScore() + ", tags=" + Arrays.deepToString(getTags()) + ")";
  }
  
  ValueExample withAge(int age) {
    return this.age == age ? this : new ValueExample(name, age, score, tags);
  }
  
  public static final class Exercise<T> {
    private final String name;
    private final T value;
    
    private Exercise(String name, T value) {
      this.name = name;
      this.value = value;
    }
    
    public static <T> Exercise<T> of(String name, T value) {
      return new Exercise<T>(name, value);
    }
    
    public String getName() {
      return this.name;
    }
    
    public T getValue() {
      return this.value;
    }
    
    @java.lang.Override
    public boolean equals(Object o) {
      if (o == this) return true;
      if (!(o instanceof ValueExample.Exercise)) return false;
      final Exercise<?> other = (Exercise<?>)o;
      final Object this$name = this.getName();
      final Object other$name = other.getName();
      if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false;
      final Object this$value = this.getValue();
      final Object other$value = other.getValue();
      if (this$value == null ? other$value != null : !this$value.equals(other$value)) return false;
      return true;
    }
    
    @java.lang.Override
    public int hashCode() {
      final int PRIME = 59;
      int result = 1;
      final Object $name = this.getName();
      result = result * PRIME + ($name == null ? 43 : $name.hashCode());
      final Object $value = this.getValue();
      result = result * PRIME + ($value == null ? 43 : $value.hashCode());
      return result;
    }
    
    @java.lang.Override
    public String toString() {
      return "ValueExample.Exercise(name=" + getName() + ", value=" + getValue() + ")";
    }
  }
}

@Builder

La anotación @Builder produce APIs constructoras complejas para sus clases.

@Singular

Anotando uno de los parámetros (si se anota un método o constructor con @Builder) o campos (si se anota una clase con @Builder) con la anotación @Singular, lombok tratará ese nodo constructor como una colección, y generará 2 métodos ‘adder‘ en lugar de un método ‘setter‘. Uno que añade un único elemento a la colección, y otro que añade todos los elementos de otra colección a la colección. No se generará ningún método setter que simplemente establezca la colección (reemplazando lo que ya se haya añadido). También se genera un método ‘clear‘. Estos constructores ‘singulares‘ son muy complicados para garantizar las siguientes propiedades:

Al invocar a build(), la colección producida será inmutable.
La llamada a uno de los métodos ‘adder’, o al método ‘clear‘, después de invocar a build() no modifica ningún objeto ya generado, y, si posteriormente se vuelve a llamar a build(), se genera otra colección con todos los elementos añadidos desde la creación del constructor.
La colección producida se compactará al formato más pequeño posible sin dejar de ser eficiente.
@Singular sólo puede aplicarse a tipos de colección conocidos por lombok. Actualmente, los tipos soportados son:

  • java.util:
    • Iterable, Collection y List (respaldada por una ArrayList compactada no modificable en el caso general).
      • Set, SortedSet y NavigableSet (respaldados por un HashSet o TreeSet no modificable de tamaño inteligente en el caso general).
      • Map, SortedMap y NavigableMap (respaldados por un HashMap o TreeMap no modificable de tamaño inteligente en el caso general).
  • Guava com.google.common.collect:
    • ImmutableCollection e ImmutableList (respaldados por la función constructora de ImmutableList).
    • ImmutableSet e ImmutableSortedSet (respaldados por la función de construcción de estos tipos).
    • ImmutableMap, ImmutableBiMap e ImmutableSortedMap (respaldados por la función de construcción de estos tipos).
    • ImmutableTable (respaldado por la función constructora de ImmutableTable).

Con Lombok

import lombok.Builder;
import lombok.Singular;
import java.util.Set;

@Builder
public class BuilderExample {
  @Builder.Default private long created = System.currentTimeMillis();
  private String name;
  private int age;
  @Singular private Set<String> occupations;
}

Sin Lombok

import java.util.Set;

public class BuilderExample {
  private long created;
  private String name;
  private int age;
  private Set<String> occupations;
  
  BuilderExample(String name, int age, Set<String> occupations) {
    this.name = name;
    this.age = age;
    this.occupations = occupations;
  }
  
  private static long $default$created() {
    return System.currentTimeMillis();
  }
  
  public static BuilderExampleBuilder builder() {
    return new BuilderExampleBuilder();
  }
  
  public static class BuilderExampleBuilder {
    private long created;
    private boolean created$set;
    private String name;
    private int age;
    private java.util.ArrayList<String> occupations;
    
    BuilderExampleBuilder() {
    }
    
    public BuilderExampleBuilder created(long created) {
      this.created = created;
      this.created$set = true;
      return this;
    }
    
    public BuilderExampleBuilder name(String name) {
      this.name = name;
      return this;
    }
    
    public BuilderExampleBuilder age(int age) {
      this.age = age;
      return this;
    }
    
    public BuilderExampleBuilder occupation(String occupation) {
      if (this.occupations == null) {
        this.occupations = new java.util.ArrayList<String>();
      }
      
      this.occupations.add(occupation);
      return this;
    }
    
    public BuilderExampleBuilder occupations(Collection<? extends String> occupations) {
      if (this.occupations == null) {
        this.occupations = new java.util.ArrayList<String>();
      }

      this.occupations.addAll(occupations);
      return this;
    }
    
    public BuilderExampleBuilder clearOccupations() {
      if (this.occupations != null) {
        this.occupations.clear();
      }
      
      return this;
    }

    public BuilderExample build() {
      // complicated switch statement to produce a compact properly sized immutable set omitted.
      Set<String> occupations = ...;
      return new BuilderExample(created$set ? created : BuilderExample.$default$created(), name, age, occupations);
    }
    
    @java.lang.Override
    public String toString() {
      return "BuilderExample.BuilderExampleBuilder(created = " + this.created + ", name = " + this.name + ", age = " + this.age + ", occupations = " + this.occupations + ")";
    }
  }
}

@SneakyThrows

@SneakyThrows se puede utilizar para lanzar disimuladamente excepciones comprobadas sin declararlo en la cláusula throws del método. Por supuesto, esta capacidad algo polémica debe usarse con cuidado. El código generado por lombok no ignorará, envolverá, reemplazará o modificará de otra manera la excepción verificada lanzada; simplemente engaña al compilador. A nivel de la JVM (archivo de clase), todas las excepciones, comprobadas o no, pueden ser lanzadas independientemente de la cláusula throws de tus métodos, que es por lo que esto funciona.

Los casos de uso común para cuando se desea optar por el mecanismo de excepción comprobada se centran en torno a 2 situaciones:

  • Una interfaz innecesariamente estricta, como Runnable – cualquier excepción que se propague fuera de tu método run(), comprobada o no, será pasada al manejador de excepciones no manejadas del Thread. Atrapar una excepción verificada y envolverla en algún tipo de RuntimeException sólo está ocultando la causa real del problema.
  • Una excepción ‘imposible’. Por ejemplo, new String(someByteArray, "UTF-8"); declara que puede lanzar una UnsupportedEncodingException pero según la especificación de la JVM, UTF-8 debe estar siempre disponible. Una UnsupportedEncodingException aquí es tan probable como un ClassNotFoundError cuando usas un objeto String, ¡y tampoco los atrapas!

Con Lombok


import lombok.SneakyThrows;

public class SneakyThrowsExample implements Runnable {
  @SneakyThrows(UnsupportedEncodingException.class)
  public String utf8ToString(byte[] bytes) {
    return new String(bytes, "UTF-8");
  }
  
  @SneakyThrows
  public void run() {
    throw new Throwable();
  }
}

Sin Lombok

import lombok.Lombok;

public class SneakyThrowsExample implements Runnable {
  public String utf8ToString(byte[] bytes) {
    try {
      return new String(bytes, "UTF-8");
    } catch (UnsupportedEncodingException e) {
      throw Lombok.sneakyThrow(e);
    }
  }
  
  public void run() {
    try {
      throw new Throwable();
    } catch (Throwable t) {
      throw Lombok.sneakyThrow(t);
    }
  }
}