📜 ⬆️ ⬇️

Project Lombok, or declare war on a boilerplate

I will not open America, but Pandora's box: there is a lot of boilerplate in Java code. Typical getters, setters and constructors, lazy initialization methods, toString methods, hashCode, equals, exception handlers that are never thrown, thread closures, synchronization blocks. The problem is not even to write all this - modern development environments cope with such tasks by pressing a few keys. Difficulty in keeping the boilerplate up to date as modifications are made to the code. And in some cases (multithreading, implementation of hashCode and equals methods) and the template code itself to write without errors is far from an easy task. One of the solutions to the problem is code generation, and in this article I will talk about the Lombok project - a library that not only can save you from the boilerplate, but also do it as transparently as possible, with minimal configuration and, notably, support at the development environment level. .

Connecting Lombok


Lombok uses the Java 6 annotation processing engine, which implies its minimal environmental requirements. To connect Lombok to the project, it is enough to include it in dependencies. In the case of Maven, this is done as follows:

<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>0.11.0</version> <scope>provided</scope> </dependency> 

For most Lombok functionality, this library is only needed at compile time. The latest version of Lombok currently (0.11.0) has not yet got into the central repository of Maven, but you can easily install it into a local or corporate repository by downloading from the site.

We say goodbye to accessors


One of the main sources of boilerplate in Java is the lack of properties at the language level. In observance of the principles of OOP, for each declaration of a field, it is necessary to write at least six typical lines - a getter and a setter. Some libraries, like Spring or Tapestry, for their own purposes in some cases allow the developer to forget about the accessors, inserting them independently into the byte code. Lombok offers similar functionality.
')
  public class GetterSetterExample { @Getter @Setter private int age = 10; @Setter(AccessLevel.PROTECTED) private String name; @Getter(lazy=true) private final Map<String, String> map = initMap(); } 

The lazy = true parameter of the Getter annotation allows you to implement a lazy field initialization: the initMap () method call in this case will be postponed until the first getter call and wrapped in a thread-safe initialization in the form of a double-check lock .

Destruction of designers


The constructors of the POJO-classes also do not differ in complexity and diversity - most often we need something from this list: a constructor without parameters, a constructor with all parameters, a constructor with only some required parameters, a static factory method. Lombok easily handles this task with the annotations @NoArgsConstructor, @AllArgsConstructor, @RequiredArgsConstructor and the staticName parameter, respectively.

 @RequiredArgsConstructor(staticName = "of") @AllArgsConstructor(access = AccessLevel.PROTECTED) public class ConstructorExample<T> { private String name; @NonNull private T description; } 

Here is what we get as a result:

 public class ConstructorExample<T> { private String name; @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({"name", "description"}) protected ConstructorExample(String name, T description) { if (description == null) throw new NullPointerException("description"); this.name = name; this.description = description; } 


We generate type methods: toString, hashCode, equals


Quite a lot has been written about the proper implementation of the equals and hashCode methods - perhaps it is worth recalling on this subject Blok's “Effective Java” and the article of Oderski . In short, it can be said that implementing them correctly is not easy, keeping them up to date is even more difficult, and they may well occupy a good half of the class. The toString method is not so critical for correct code, but it is not enough to update it every time a class is changed. Let's give Lombok the opportunity to do this ungrateful work for us with the help of two simple annotations:

 @ToString(exclude="id") @EqualsAndHashCode(exclude="id") public class Person { private Long id; private String name; ... } 


Invisible Logger


If you use one of the popular logging libraries, then, most likely, in every class you have a static logger declaration:

 public class Controller { private static final Logger log = LoggerFactory.getLogger(Controller.class); public void someMethod() { log.debug("someMethod started"); 

Instead, Lombok suggests using Log , @CommonsLog, @ Log4j or @ Slf4j annotations - depending on the preferred logging tool:

 @Slf4j public class Controller { public void someMethod() { log.debug("someMethod started"); 


We finalize local variables.


A good programming style is the use of final local variables, however, given the static typing and the lack of Java type inference, the declaration of some particularly tricked map may well get off the screen. If you don’t want to switch to Scala or Groovy from Java yet, you can use the following Lombok hack:

 public class ValExample { public String example() { val map = new HashMap<String, Map<String, String>>(); for (val entry: map.entrySet()) { ... 

The variable map in this case will be declared as final, while the description of its type will be taken from the right side of the assignment expression.

We throw exceptions with impunity


Not all developers, unfortunately, have read the Block “Effective Java” mentioned above or Stelting's “Robust Java” mentioned here. Or maybe they read it, but not very carefully. Or, perhaps, they really had some kind of well-founded motivation to declare this particular exception as verifiable - but this is no better for you, because you know that it will never occur! What to do, for example, with UnsupportedEncodingException, if you are absolutely sure that without system support for UTF-8 encoding your application will not work anyway? You have to enclose the code in try-catch and write a meaningless output to the log, which never waits in an hour, override the exception in a runtime wrapper that is not destined to be born, or ignore it altogether with an empty interception block (which, I don’t know how you, and I personally always have a desire to grab the revolver). Lombok offers an alternative here too.

  public class SneakyThrowsExample implements Runnable { @SneakyThrows // "  !" public void run() { throw new Throwable(); // "   !" } } 

For this witch, in contrast to everything else, you will need to connect Lombok in runtime. It's simple: Lombok puts the compiler to vigilance by catching an exception in try, and then in runtime imperceptibly re-throws it into catch. The trick is that at the bytecode level, you can throw any exception, even one that is not declared in the method signature.

Proper timing


Multithreading is a very complex programming area with its idiomatic patterns and patterns in Java. One of the correct practices is to use private final fields for synchronization, since any unrelated piece of functionality may be synchronized on any publicly accessible lock box, which will lead to unnecessary locks and difficult to catch errors. Lombok can correctly synchronize the contents of the method marked with the @Synchronized annotation, both static and instance methods:

 @Synchronized public static void hello() { System.out.println("World"); } @Synchronized public int answerToLife() { return 42; } 

Here is what we get:

 private static final Object $LOCK = new Object[0]; private final Object $lock = new Object[0]; public static void hello() { synchronized($LOCK) { System.out.println("world"); } } public int answerToLife() { synchronized($lock) { return 42; } } 


And what does IDE say?


All these wonderful pieces would not be worth anything if, when opening a project in Eclipse or IntelliJ IDEA, the lines of code would flare up with a red flame from the righteous anger of the compiler. Fortunately, integration with development environments is available, and quite good. For IntelliJ IDEA, the plugin is present in the standard repository:



For Eclipse and NetBeans, the installation is a bit unusual. You need to run the lombok.jar file and it will show a nice installer offering to roll Lombok onto existing Eclipse installations:



These plug-ins not only convince the development environment that it only seems to lack the getters, setters and other elements of the boilerplate - they also correctly highlight erroneous situations, for example, when the getter that is to be generated is already present in the class:



I listed the main pieces of Lombok, but that's not all. In more detail all possible annotations with all attributes and with the comparison of the “before” and “after” code are described in the documentation .

Source: https://habr.com/ru/post/142356/


All Articles