Lombok project, or Declare war on a boilerplate

    I’ll open not America, but the Pandora’s box: there are a lot of boilerplate in the Java code. Typical getters, setters and constructors, methods of lazy initialization, methods toString, hashCode, equals, exception handlers that are never thrown, thread closers, synchronization blocks. The problem is not even to write all this - modern development environments cope with such tasks by pressing a few keys. The 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 writing the template code itself without errors is far from an easy task. One solution to the problem is code generation, and in this article I will talk about the Lombok project - a library that can not only save you from a boilerplate, but also make it as transparent as possible, with a minimum configuration and, importantly, with support at the development environment level.

    Connect Lombok


    Lombok uses the annotation processing engine from Java 6, from which its minimal environmental requirements follow. To connect Lombok to the project, just turn it on depending. In the case of using Maven, this is done as follows:

    org.projectlomboklombok0.11.0provided

    For most of Lombok's functionality, this library is only needed at the compilation stage. The latest version of Lombok at the moment (0.11.0) has not yet reached the central repository of Maven, however, it can be installed without problems to 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 compliance with the principles of OOP, at least six typical lines — getter and setter — have to be written for each field declaration. Some libraries, such as Spring or Tapestry, for their purposes in some cases allow the developer to forget about accessors by inserting them into the bytecode themselves. Similar functionality is offered by Lombok.

        public class GetterSetterExample {
            @Getter @Setter private int age = 10;
            @Setter(AccessLevel.PROTECTED) private String name;
            @Getter(lazy=true) private final Map map = initMap();
        }
    

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

    Destruction of designers


    Constructors of POJO classes do not differ in complexity and variety either - 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 {
        private String name;
        @NonNull private T description;
    }
    

    Here is what we get as a result:

    public class ConstructorExample {
        private String name;
        @NonNull private T description;
        private ConstructorExample(T description) {
            if (description == null) throw new NullPointerException("description");
            this.description = description;
        }
        public static  ConstructorExample of(T description) {
            return new ConstructorExample(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 standard methods: toString, hashCode, equals


    Quite a lot has been written about the correct implementation of the equals and hashCode methods - perhaps it’s worth recalling Blok ’s Effective Java and Odersky's article on this subject . In short, we can say that implementing them correctly is not easy, keeping them up to date is even more difficult, and they can occupy a good half of the class. The toString method is not so critical for the correctness of the code, but updating it every time you change the class is also not very pleasant. We give Lombok the opportunity to do this thankless job 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 each 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 the annotations Log , @CommonsLog, @ Log4j or @ Slf4j, depending on your preferred logging facility:

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


    Finalize local variables


    The use of final local variables is a good programming style, however, given the static typing and the absence of type inference in Java, declaring some particularly sophisticated map may well creep out of 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>();
            for (val entry: map.entrySet()) {
            ...
    

    The map variable 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.

    Throw exceptions with impunity


    Not all developers, unfortunately, read the “Effective Java” block or Robust Java already mentioned hereStelting. Or maybe they read, but not very carefully. Or, perhaps, they really had some kind of well-founded motivation to declare this particular exception to be checked - but this is not easier for you, because you know that it will never arise! What to do, for example, with UnsupportedEncodingException, if you are absolutely sure that without system support for UTF-8 encoding your application will still not work? You have to wrap the code in a try-catch and write senseless output to a log that will never wait, turn an exception into a runtime wrapper that is not destined to be born, or even ignore it with an empty intercept block (which I don’t know how you, and I personally always arouse the desire to grab the revolver). Lombok here offers an alternative.

        public class SneakyThrowsExample implements Runnable {
            @SneakyThrows // "Здесь исключений нет!"
            public void run() {
                throw new Throwable(); // "Ёжик исключения не брал!"
            }
        }
    

    For this witchcraft, unlike everything else, you will need to connect Lombok in runtime. It's simple: Lombok lulls the compiler's vigilance by catching an exception in try, and then in runtime discreetly 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 sync


    Multithreading is a very complex programming area, which has its own idioms and patterns in Java. One of the best practices is to use private final fields for synchronization, as any unconnected piece of functionality may want to be synchronized on any public lock, which will lead to unnecessary locks and hard-to-catch errors. Lombok can correctly synchronize the contents of a 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's 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 the IDE say?


    All these wonderful features would have been worthless if, when opening a project in Eclipse or IntelliJ IDEA, lines of code would have flared up in red flame from the compasser's righteous anger. 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 she just seems to have no getters, setters and other boilerplate elements - they also correctly highlight the wrong ones situations, for example, when a getter to be generated is already present in the class:



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

    Also popular now: