Escribe menos código tedioso, con Lombok

Lombok es una biblioteca de software para Java, que contiene varias transformaciones aplicadas al código en tiempo de compilación, ayudándonos con varias de las tareas más tediosas de la programación en Java.

El Jar es además ejecutable, contiene un programa que detecta los IDE's instalados, como Eclipse (y los basados en Eclipse como STS), IDEA y me parece que NetBeans. Esto es necesario en el IDE para la compilación, pero en realidad si se usa un editor de texto regular para programar, o un sistema automatizado de construcción de proyectos, basta con agregar lombok.jar al classpath de compilación.

La versión más reciente es la 0.10.0 que trae algunas monerías adicionales a las que ya se incluían en versiones anteriores y además ya no requiere incluir el Jar en tiempo de ejecución; todas las transformaciones se realizan en tiempo de compilación.

También ya es compatible con Java 7.

Bueno pero, ¿qué son estas transformaciones? Vienen en forma de anotaciones, y creo que la manera más fácil de entenderlas es con ejemplos.

Propiedades, Beans

Empecemos por las anotaciones para propiedades y beans. En vez de esto:

public class Ejemplo {
  private String algo;
  public void setAlgo(String value) {
    algo = value;
  }
  public String getAlgo() {
    return algo;
  }
}

Simplemente puedes hacer esto:

public class Ejemplo {
  @Getter @Setter private String algo;
}

La anotación @Setter va sobre una variable de instancia, y causa que se genere un método para modificarla. La anotación @Getter también va sobre una variable de instancia y causa que se genere un método para obtenerla.

Si tienen un bean, que sólo contiene datos, en vez de tener que generar getters y setters con el IDE, pueden usar estas anotaciones; pero de hecho si quieren getter y setter para todas las propiedades del objeto, así como los métodos equals, hashCode y toString, entonces simplemente pueden usar la anotación @Data:

@Data
public class Ejemplo {
  private String nombre;
  private Date fecha;
}

@Data
public class Otro {
  private final String nombre;
  private int entero;
}

La clase Ejemplo será un bean común y corriente, con su constructor default, los getters y setters para nombre y fecha, y tendrá los métodos equals, hashCode y toString.

La clase Otro tendrá un constructor que recibe una cadena que se asigna a nombre, tendrá solamente getter para nombre, getter y setter para entero, y los 3 métodos que también tiene Ejemplo.

Estas son de las cosas que más utilizo de Lombok. No tener que volver a escribir un setter ni un getter me ahorra algo de tiempo, porque no solamente se trata de escribirlo o generarlo con el IDE; si posteriormente cambiamos el nombre de la propiedad, o queremos cambiar su visibilidad o hacerla de sólo lectura, etc, hay que mantener ese código y si se olvida modificarlo puede ser una fuente de errores.

Constructores

Otra anotación muy útil que uso cuando tengo algunos Runnables o clases que se usan para ejecutar tareas y luego desecharlas es @RequiredArgsConstructor:

@RequiredArgsConstructor
public class Tarea {
  private final Algo algo;
  private final OtraCosa otro;
  private int estado;
}

Al momento de compilar, esta clase en vez del constructor default, tendrá un constructor que recibe un Algo y un OtraCosa, y los asignará a las propiedades marcadas como finales. La propiedad "estado" es modificable, por lo tanto no es considerada para el constructor. Si además quieren tener el constructor que tenga los 3 valores, pueden agregar la anotación @AllArgsConstructor.

Si quieren implementar el patrón factory, una de estas anotaciones les basta, solamente tienen que agregar un modificar. Y pueden anotar las propiedades que no deben aceptar nulos:

@AllArgsConstructor(staticName="nuevo")
public class Fabricado {
  private String opcional;
  @NonNull private Date obligatorio;
}

La clase Fabricado tendrá un constructor privado que acepta una cadena y una fecha, así como un método estático nuevo(String,Date) que devuelve instancias de Fabricado con los valores especificados. Si se quiere crear una instancia con Fabricado.nuevo("hola", null), se arroja una NPE porque el campo obligatorio no acepta nulos.

Manejo de recursos

Todos conocemos el patrón de uso de recursos como streams y conexiones: manejarlos dentry de un try-catch-finally o al menos un try-finally, cerrándolos en el finally:

public void algoConJdbc() throws SQLException {
  Connection conn = //obtener una conexion
  try {
    PreparedStatement ps = conn.prepareStatement("blabla");
    ps.executeUpdate();
  } finally {
    if (conn != null) {
      conn.close();
    }
  }
}

public String leerArchivo(String path) throws IOException {
  FileReader reader = new FileReader(path);
  try {
    //leer del archivo y crear una cadena
    return cadena;
  } finally {
    if (reader != null) {
      reader.close();
    }
  }
}

Incluso hay quienes tienen código así, medio genérico, para abrir archivos o hacer algo con una conexión de JDBC, y evitarse así la talacha de estar escribiendo eso una y otra vez. Lombok nos ofrece una anotación para ahorrarnos todo eso. Este código, al compilarse, queda igual que el anterior:

public void algoConJdbc() throws SQLException {
  @Cleanup Connection conn = //obtener la conexion
  PreparedStatement ps = conn.prepareStatement("blabla");
  ps.executeUpdate();
}

public String leerArchivo(String path) throws IOException {
  @Cleanup FileReader reader = new FileReader(path);
  //leer archivo
  return cadena;
}

El código que nunca tuviste que escribir es código que nunca tendrás que mantener (ni tú ni nadie más).

Excepciones

El manejo de excepciones es algo que nos trae de cabeza a muchos. Con las RuntimeExceptions pues podemos de repente hacernos de la vista gorda, pero con las Exception, o las cachamos, o las tenemos que declarar y se hace una cadena en donde alguien en algún momento debe manejarla. Y se dan casos en donde hay excepciones que SABEMOS que no van a ocurrir, pero como estamos invocando código que las declara, tenemos que manejarlas, y poner un try-catch y comentar en el catch "ESTO NUNCA OCURRIRA" y explicar por qué.

Un ejemplo simple: tenemos un arreglo de bytes que queremos convertir a una cadena usando UTF-8. El constructor de String que recibe un encoding arroja UnsupportedEncodingException, por si le pasamos un encoding que no tenga la JVM. Pero también sabemos que la JVM viene incluida siempre con UTF-8, de modo que en ese caso específico, en la práctica nunca se arrojará la excepción. Entonces, en vez de tener que declararla, o cacharla y no hacer nada, podemos usar una anotación de Lombok:

@SneakyThrows(UnsupportedEncodingException.class)
public String bytesComoCadena(byte[] bytes) {
  return new String(bytes, "UTF-8");
}

La anotación @SneakyThrows debe usarse indicando la clase de excepción que queremos enmascarar. Lo que hace esta anotación no es transformar el código para meter un try-catch que no haga nada; simplemente oculta la excepción al compilador pero si en tiempo de ejecución se arroja, será arrojada de manera normal. Termina ocurriendo como en Groovy o Scala, donde todas las excepciones son de tiempo de ejecución y por lo tanto sólo ponemos try-catch cuando vamos a manejar la excepción que se arroja.

val: Inferencia de tipos en variables finales

Cuando tenemos variables locales finales, el código puede ser algo redundante:

final String cadena = "algo";
final ArrayList<String> lista = new ArrayList<String>();

En el segundo caso, Java 7 lo más que puede hacer por nosotros es ayudarnos a no teclear el segundo String. Pero Lombok nos ofrece algo mejor: la palabra val, que funciona de manera similar esa palabra reservada de Scala: marca la variable como final, y hace inferencia del tipo que se le asigna. De modo que esto nos resulta en el mismo código compilado que lo anterior:

val cadena = "algo";
val lista = new ArrayList<String>();

Conclusión

Lombok nos puede ayudar a evitar el tener que escribir varias de las partes más tediosas de nuestro código Java. Y dado que no agrega dependencias en tiempo de compilación, puede ser utilizada sin muchos problemas, sólo se requiere para compilar. Adicionalmente, la integración con Eclipse y NetBeans es muy buena, y existen plugins para IDEA y probablemente otros IDE's.

No he mencionado todas las anotaciones que incluye Lombok, solamente lo que creo que puede ayudar de manera más inmediata a la mayoría de los programadores. Espero lo encuentren tan útil como yo.

Comentarios

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.

Como Seria con Closures

Algunba vez trabajando con Hibernate + PostgreSQL tuve que deshacerme de Loombok porque a los setter debia ponerles:

public set String setCosa(String cosa){
    this.cosa = (cosa==null ? cosa : cosa.trim)
}

Porque el posgre me regresaba:

"cosa                "
"alguna cosa         "
"otra cosa           "

y claro que hacerle el trim() fue obligatorio para los Strings. Por lo que se me ocurre que seria genial agregarle funcionalidad adicional a los setters que Lombok genera

Quizas mal implementado pero me imaginaria algo asi

@Data(
    Setter([AccessLevel.PUBLIC,] type="String", apply:"trimString")
)
public class Cosa {

        private String
                cosaA,
                cosaB,
                cosaC;
   
        private String trimString(String text){
                return (text == null || text.isEmpty() ? text : text.trim());
        }
       
}

Quizas internamente sea convertir esa funcion definida en apply y la pueda convertir en closure y aplicarla al setter, aunque solo funcionaria en Java 7

Imagen de ezamudio

estaría bien

Aunque suena como que "es culpa de postgreSQL" cuando en realidad ese problema ocurre con cualquier base de datos, cuando usas columnas CHAR en vez de VARCHAR. Y no entiendo por qué tuvieron que deshacerse de Lombok por completo; ese problema que mencionas no afecta que puedas usar las demás anotaciones @ToString, @RequiredArgsConstructor, @SneakyThrows, @Cleanup, etc...

Lombok me dió pretexto para agregar annotaciones.

Cuando ví tu tweet hace varias semanas sobre Lombok me animé para agregar anotaciones a Ryz y como todo termina siendo bytecode al final lo siguiente me funcionó muy bien:

import ( lombok.* )
@Data
demo.lombok.Persona {
   name     : String
   lastName : String
   age      : Integer
}

Está genial tener estas anotaciones.

Aunque lo revisé en su momento y quedé tranquilo, ahora que lo vuelvo lo que me inquieta es el tipo de dato val que según recuerdo es un tipo de dato Java válido y lo único que hace esto es que al momento de compilar lo sustituye. Digo que me hace ruido porque en realidad hace que esto no parezca Java válido ( estrictamente hablando es un error de compilación ) sino un subset de Java.

Magnífico!

Mira que me he topado con LagunaMentalException al olvidar escribir ciertos getter/setter, aunque no propiamente en un bean, sino en un servicio, sin embargo todas las funcionalidades que mencionas se ven estupendas. Aunque el manejo de shortcuts del IDE ayudan a generar los getters/setters y bloques try/catch, pero nada como tener una clase limpia y fácil de mantener.

Muchas gracias por el dato.

Imagen de greeneyed

Yo lo uso desde hace un

Yo lo uso desde hace un tiempo y la verdad es que va bastante bien para ahorrarse "boilerplate". Sólo un apunte, con la 0.9.3 tampoco hacía falta añadir el .jar en tiempo de ejecución excepto si usabas SneakyThrows, cosa que ahora con la 10.0.0 han arreglado.

Yo llegué a extender Lombok con algunas anotaciones propias, aunque lo dejé con soporte únicamente para javac y hay que decir que es un trabajazo lo que hace la librería, por que los APIS con los que trabajan no están bien documentados, cambian de version a version, cada uno a la suya... tiene un mérito de la leche.

Para proximas versiones, lo que quieren hacer es un paso intermedio donde tu escribiras tus anotaciones/extensiones en un API de Lombok y un transformador ya creará las versiones adecuadas para javac, ecj... y entonces retomaré mis extensiones (lo dejé cuando supe sus planes por qué el API de eclipse es un rollo y depurarlo una pesadilla).

No estaría mal que los del JDK le echasen un ojo a lo que hacen por que algunas buenas ideas podrían sacar.