The correct posture to use Lombok

In the world of Java development, the Lombok plugin has become a very popular code library. The plug-in makes Java development easier and more efficient, thus increasing developer productivity.

1. What is Lombok?

Lombok is a Java development tool that can help programmers automatically generate Java code at compile time through annotations, thus simplifying the Java development process. Specifically, he will enhance the functionality of Java code by parsing annotations and then generating code at compile time.

The main reason for the Lombok plugin is the bloated syntax of the Java language, which requires a lot of boilerplate code, as well as verbose and bloated getter and setter methods. When your model layers are very large, writing all this code manually can become very tedious and boring. Therefore, the Lombok plug-in automatically generates Java code for us and helps optimize the Java development process and improve efficiency.

2. Install Lombok

Open the IDEA settings page:

Search for "Lombok" on the plugin page to install:

Note: To use Lombok, you must install the Lombok plug-in in the IDE, so that the IDE can correctly recognize and use Lombok annotations.

3. Spring Boot integrates Lombok

In order to use the Lombok plugin, we need to set dependencies in the project. Here's an example of adding Lombok's pom.xml file using Maven:

<!--Lombok-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

Note: After SpringBoot 2.1.x, there is no need to specify the Lombok version. SpringBoot spring-boot-dependencieshas built-in this dependency for management.

4. Use Lombok

4.1 List of annotations

The following are some of the main annotations provided by Lombok and their functions:

annotation Function
@Getter Generate getter methods for fields
@Setter Generate setter methods for fields
@Data Generate getter and setter methods for fields, also includes equals, hashCode, toString methods
@ToString Generate a toString method for the class
@EqualsAndHashCode Generate equals and hashCode methods for classes
@NoArgsConstructor Generate a no-argument constructor for a class
@AllArgsConstructor Generate a constructor with all fields for a class
@RequiredArgsConstructor @NonNullGenerate constructors for classes with required fields (final and annotated fields)
@Value Generate read-only properties (final) for classes, including getter, equals, hashCode, toString methods
@Builder Implement the Builder pattern for classes
@SneakyThrows Used on a method, you can let the method throw a checked exception without explicitly using the throws keyword on the method
@Slf4j Add a SLF4J logger object called 'log' to the class
@Cleanup Automatically manage resources, used to automatically call the close method to release resources
@NonNull Used to check whether the parameter of the method or constructor is null, and if it is null, NullPointerException will be thrown
@Delegate Automatically generate a delegate method to forward the call to the specified field or method
@With Generate a method for a field that returns a new object with an updated value for a field
@SuperBuilder Builder pattern for more complex inheritance cases
@Synchronized The synchronization lock provided by Lombok, used for methods, will be synchronized on a private field, if it is a static method, it will be synchronized on a private static field
val Define a local variable and assign a value to it immediately. This variable is of final type and cannot be modified

4.2 Partial use introduction

Most of the above comments can be basically understood by looking at the function description, and some of the less easy-to-understand instructions are picked out below.

@Getter(lazy=true)

@Getter(lazy=true)Generate lazy-initialized getter methods for fields. This getter method initializes the field the first time it is called, and caches the result. Generally, when a certain attribute that needs to be obtained consumes more resources, you can add lazy=trueattributes through this annotation to implement lazy loading, which will generate Double Check Lock boilerplate code to lazy load the attribute.

In Java, the "double check lock" pattern is a common multi-threaded concurrent programming technique, mainly used to delay initialization and ensure that only one thread can initialize resources. When you use annotations in Lombok @Getter(lazy=true), Lombok will generate the corresponding double-checked lock code. This ensures lazy initialization of fields, thread safety in a multi-threaded environment, and initialization only once.

import lombok.Getter;

public class LazyGetterExample {
    
    

    // 使用双重检查锁对方法进行加锁,确保只有一个线程可以执行 expensive() 方法(懒加载)
    @Getter(lazy = true)
    private final Double cached = expensive();

    private Double expensive() {
    
    
        // 模拟一个耗时的操作
        Double result = null;
        for (int i = 0; i < 1000000; i++) {
    
    
            result = Math.atan(i) * Math.tan(i);
        }
        return result;
    }

    public static void main(String[] args) {
    
    
        LazyGetterExample example = new LazyGetterExample();
        System.out.println(example.getCached());
    }
}

The compiled code will look like this:

import java.util.concurrent.atomic.AtomicReference;

public class LazyGetterExample {
    
    
    private final AtomicReference<Object> cached = new AtomicReference();

    public LazyGetterExample() {
    
    
    }

    private Double expensive() {
    
    
        Double result = null;

        for(int i = 0; i < 1000000; ++i) {
    
    
            result = Math.atan((double)i) * Math.tan((double)i);
        }

        return result;
    }

    public static void main(String[] args) {
    
    
        LazyGetterExample example = new LazyGetterExample();
        System.out.println(example.getCached());
    }

    public Double getCached() {
    
    
        Object value = this.cached.get();
        if (value == null) {
    
    
            synchronized(this.cached) {
    
    
                value = this.cached.get();
                if (value == null) {
    
    
                    Double actualValue = this.expensive();
                    value = actualValue == null ? this.cached : actualValue;
                    this.cached.set(value);
                }
            }
        }

        return (Double)((Double)(value == this.cached ? null : value));
    }
}

@Value

@ValueGenerates read-only properties for classes, that is, all fields are final, including gettermethods, equals, hashCode, toStringmethods. At this time, this class is equivalent to finalthe class and cannot be inherited, and its attributes will also become finalattributes.

@Value
public class ValueExample {
    
    
    private String name;
    private int age;

    public static void main(String[] args) {
    
    
        // 只能使用全参构造器
        ValueExample example = new ValueExample("张三", 18);
        // 可以直接获取属性值
        System.out.println(example.getName());
        System.out.println(example.getAge());

        // 不能修改属性值
        // example.setName("李四");
        // example.setAge(20);
    }
}

The compiled code will look like this:

public final class ValueExample {
    
    
    private final String name;
    private final int age;

    public static void main(String[] args) {
    
    
        ValueExample example = new ValueExample("张三", 18);
        System.out.println(example.getName());
        System.out.println(example.getAge());
    }

    public ValueExample(final String name, final int age) {
    
    
        this.name = name;
        this.age = age;
    }

    public String getName() {
    
    
        return this.name;
    }

    public int getAge() {
    
    
        return this.age;
    }

    public boolean equals(final Object o) {
    
    
        if (o == this) {
    
    
            return true;
        } else if (!(o instanceof ValueExample)) {
    
    
            return false;
        } else {
    
    
            ValueExample other = (ValueExample)o;
            if (this.getAge() != other.getAge()) {
    
    
                return false;
            } else {
    
    
                Object this$name = this.getName();
                Object other$name = other.getName();
                if (this$name == null) {
    
    
                    if (other$name != null) {
    
    
                        return false;
                    }
                } else if (!this$name.equals(other$name)) {
    
    
                    return false;
                }

                return true;
            }
        }
    }

    public int hashCode() {
    
    
        int PRIME = true;
        int result = 1;
        result = result * 59 + this.getAge();
        Object $name = this.getName();
        result = result * 59 + ($name == null ? 43 : $name.hashCode());
        return result;
    }

    public String toString() {
    
    
        return "ValueExample(name=" + this.getName() + ", age=" + this.getAge() + ")";
    }
}

@Builder

@BuilderImplement the Builder design pattern (builder pattern) for classes, usually used for chained construction of complex objects.

import lombok.Builder;
import lombok.ToString;

@ToString
@Builder
public class BuilderExample {
    
    
    private String name;
    private int age;

    public static void main(String[] args) {
    
    
        BuilderExample example = BuilderExample.builder().name("张三").age(18).build();
        System.out.println(example);
    }
}

The compiled code will look like this:

public class BuilderExample {
    
    
    private String name;
    private int age;

    public static void main(String[] args) {
    
    
        BuilderExample example = builder().name("张三").age(18).build();
        System.out.println(example);
    }

    BuilderExample(final String name, final int age) {
    
    
        this.name = name;
        this.age = age;
    }

    public static BuilderExampleBuilder builder() {
    
    
        return new BuilderExampleBuilder();
    }

    public String toString() {
    
    
        return "BuilderExample(name=" + this.name + ", age=" + this.age + ")";
    }

    public static class BuilderExampleBuilder {
    
    
        private String name;
        private int age;

        BuilderExampleBuilder() {
    
    
        }

        public BuilderExampleBuilder name(final String name) {
    
    
            this.name = name;
            return this;
        }

        public BuilderExampleBuilder age(final int age) {
    
    
            this.age = age;
            return this;
        }

        public BuilderExample build() {
    
    
            return new BuilderExample(this.name, this.age);
        }

        public String toString() {
    
    
            return "BuilderExample.BuilderExampleBuilder(name=" + this.name + ", age=" + this.age + ")";
        }
    }
}

@SuperBuilder

@SneakyThrows

@SneakyThrowsUsed on a method, you can let the method throw a checked exception without explicitly using throwsthe keyword to throw an exception on the method.

import lombok.SneakyThrows;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

public class SneakyThrowsExample {
    
    
    // 自动抛出异常,不需要手动 try catch
    @SneakyThrows(UnsupportedEncodingException.class)
    public String sneakyMethod(){
    
    
        return URLEncoder.encode("Example", "Unsupported Encoding");
    }
    
    public static void main(String[] args) {
    
    
        SneakyThrowsExample example = new SneakyThrowsExample();
        System.out.println(example.sneakyMethod());
    }
}

The compiled code will look like this:

public class SneakyThrowsExample {
    
    
    public SneakyThrowsExample() {
    
    
    }

    public String sneakyMethod() {
    
    
        try {
    
    
            return URLEncoder.encode("Example", "Unsupported Encoding");
        } catch (UnsupportedEncodingException var2) {
    
    
            throw var2;
        }
    }

    public static void main(String[] args) {
    
    
        SneakyThrowsExample example = new SneakyThrowsExample();
        System.out.println(example.sneakyMethod());
    }
}

@Slf4j

When using Lombok to generate log objects, there are various annotations that can be used depending on the log implementation. For example @Log, @Log4j, @Log4j2, @Slf4jand so on. For @Slf4jexample , it adds a SLF4J logger object called 'log' to the class.

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Slf4jExample {
    
    
    public static void main(String[] args) {
    
    
        log.info("Hello World");
        log.error("Hello World");
        log.debug("Hello World");
        log.warn("Hello World");
    }
}

The compiled code will look like this:

public class Slf4jExample {
    
    
    private static final Logger log = LoggerFactory.getLogger(Slf4jExample.class);

    public Slf4jExample() {
    
    
    }

    public static void main(String[] args) {
    
    
        log.info("Hello World");
        log.error("Hello World");
        log.debug("Hello World");
        log.warn("Hello World");
    }
}

@Cleanup

@CleanupIt is used to automatically manage resources, and is used to automatically call close()methods to release resources, avoiding manual release.

public class CleanupExample {
    
    
    public static void main(String[] args) throws IOException {
    
    
        @Cleanup InputStream in = Files.newInputStream(Paths.get("file.txt"));
    }
}

The compiled code will look like this:

public class CleanupExample {
    
    
    public CleanupExample() {
    
    
    }

    public static void main(String[] args) throws IOException {
    
    
        InputStream in = new FileInputStream("/file.txt");
        if (Collections.singletonList(in).get(0) != null) {
    
    
            in.close();
        }
    }
}

@NonNull

@NonNullIt is used to check whether the parameter of the method or constructor is null. If it is null, it will be thrown NullPointerException. It is generally used for non-null judgment.

public class NonNullExample {
    
    
    public String nonNullMethod(@NonNull String string){
    
    
        return string;
    }
    
    public static void main(String[] args) {
    
    
        NonNullExample example = new NonNullExample();
        example.nonNullMethod(null);
    }
}

The compiled code will look like this:

public class NonNullExample {
    
    
    public NonNullExample() {
    
    
    }

    public String nonNullMethod(@NonNull String string) {
    
    
        if (string == null) {
    
    
            throw new NullPointerException("string is marked non-null but is null");
        } else {
    
    
            return string;
        }
    }

    public static void main(String[] args) {
    
    
        NonNullExample example = new NonNullExample();
        example.nonNullMethod((String)null);
    }
}

@With

@WithA method for field generation that returns a new object with an updated value for a field. In simple terms, it can clone the original object and change one of its properties, and it needs to specify the full parameter construction method when using it.

import lombok.AllArgsConstructor;
import lombok.With;

@With
@AllArgsConstructor
public class WithExample {
    
    
    private String name;
    private int age;

    public static void main(String[] args) {
    
    
        // 实例化一个对象
        WithExample example = new WithExample("javgo", 18);
        System.out.println(example);

        // 使用 withName 方法修改 name 属性
        WithExample withExample = example.withName("javgo2");
        System.out.println(withExample);
    }
}

The compiled code will look like this:

public class WithExample {
    
    
    private String name;
    private int age;

    public static void main(String[] args) {
    
    
        WithExample example = new WithExample("javgo", 18);
        System.out.println(example);
        WithExample withExample = example.withName("javgo2");
        System.out.println(withExample);
    }

    public WithExample withName(final String name) {
    
    
        return this.name == name ? this : new WithExample(name, this.age);
    }

    public WithExample withAge(final int age) {
    
    
        return this.age == age ? this : new WithExample(this.name, age);
    }

    public WithExample(final String name, final int age) {
    
    
        this.name = name;
        this.age = age;
    }
}

@Synchronized

When we access the same resource in multiple threads, thread safety issues often arise, and we often use synchronizedkeyword modification methods to achieve synchronized access. Instead , the synchronization lock@Synchronized provided by Lombok is used for methods and will be synchronized on a private field. If it is a static method, it will be synchronized on a private static field.

import lombok.Synchronized;

public class SynchronizedExample {
    
    
    private final Object readLock = new Object();

    @Synchronized
    public void syncMethod(){
    
    
        // do something
    }

    @Synchronized("readLock")
    public void customLockMethod() {
    
    
        // do something
    }

    public static void main(String[] args) {
    
    
        SynchronizedExample example = new SynchronizedExample();

        // 两个方法都会被锁定
        example.syncMethod(); // 锁定的是 this, 即 SynchronizedExample 对象
        example.customLockMethod(); // 锁定的是 readLock,即 SynchronizedExample.readLock 对象
    }
}

The compiled code will look like this:

public class SynchronizedExample {
    
    
    private final Object $lock = new Object[0];
    private final Object readLock = new Object();

    public SynchronizedExample() {
    
    
    }

    public void syncMethod() {
    
    
        synchronized(this.$lock) {
    
    
            ;
        }
    }

    public void customLockMethod() {
    
    
        synchronized(this.readLock) {
    
    
            ;
        }
    }

    public static void main(String[] args) {
    
    
        SynchronizedExample example = new SynchronizedExample();
        example.syncMethod();
        example.customLockMethod();
    }
}

val

valIt is used to define a local variable of any type and assign a value to it immediately. This variable is of finaltype and cannot be modified.

import lombok.val;
import java.util.ArrayList;

public class ValExample {
    
    
    public static void main(String[] args) {
    
    
        val example = new ArrayList<String>();
        example.add("Hello, World!");
    }
}

The compiled code will look like this:

public class ValExample {
    
    
    public ValExample() {
    
    
    }

    public static void main(String[] args) {
    
    
        ArrayList<String> example = new ArrayList();
        example.add("Hello, World!");
    }
}

Guess you like

Origin blog.csdn.net/ly1347889755/article/details/130996976