Lombok 的正确使用姿势

在 Java 开发领域中,Lombok 插件已经成为一个非常流行的代码库。该插件让 Java 开发更加便捷、高效,因此提高了开发者的生产力。

1.Lombok 是什么

Lombok 是一款 Java 开发工具,它可以通过注解来帮助程序员在编译时自动生成 Java 代码,从而简化 Java 开发过程。具体来说他会通过解析注解,然后在编译时生成代码来实现 Java 代码的功能增强。

Lombok 插件产生的主要原因是 Java 语言臃肿的语法,需要大量的样板代码,以及冗长臃肿的 getter 和 setter 方法。当你的模型层非常大时,手动编写所有这些代码会变得非常繁琐和无聊。因此,Lombok 插件为我们自动生成 Java 代码并帮助优化 Java 开发过程,提高效率。

2.安装 Lombok

打开 IDEA 设置页面:

在插件页面搜索“Lombok”安装即可:

注意:使用 Lombok 必须要在 IDE 中安装 Lombok 插件,以使得 IDE 能正确识别和使用 Lombok 注解。

3.Spring Boot 集成 Lombok

为了使用 Lombok 插件,我们需要在项目中设置依赖。下面是一个使用 Maven 添加 Lombok 的 pom.xml 文件的例子:

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

注意:SpringBoot 2.1.x 版本后无需指定 Lombok 版本,SpringBoot 在 spring-boot-dependencies 中已经内置了该依赖进行管理。

4.使用 Lombok

4.1 注解一览表

以下是 Lombok 提供的一些主要注解及其功能:

注解 功能
@Getter 为字段生成 getter 方法
@Setter 为字段生成 setter 方法
@Data 为字段生成 getter 和 setter 方法,还包括 equals, hashCode, toString 方法
@ToString 为类生成 toString 方法
@EqualsAndHashCode 为类生成 equals 和 hashCode 方法
@NoArgsConstructor 为类生成无参构造器
@AllArgsConstructor 为类生成包含所有字段的构造器
@RequiredArgsConstructor 为类生成包含必须字段(final 和带有 @NonNull 注解的字段)的构造器
@Value 为类生成只读属性(final),包括 getter, equals, hashCode, toString 方法
@Builder 为类实现 Builder 模式
@SneakyThrows 用于方法上,可以让方法抛出被检查的异常,而不需要显式地在方法上使用 throws 关键字
@Slf4j 在类中添加一个名为 ‘log’ 的 SLF4J 日志器对象
@Cleanup 自动管理资源,用于自动调用 close 方法释放资源
@NonNull 用于检查方法或构造器的参数是否为 null,如果为 null 就会抛出 NullPointerException
@Delegate 自动生成委托(delegate)方法,用来将调用转发到指定的字段或方法
@With 为字段生成返回更新了某个字段值的新对象的方法
@SuperBuilder 用于更复杂的继承情况下的 Builder 模式
@Synchronized Lombok 提供的同步锁,用于方法,将会同步在一个私有的字段上,如果是静态方法,则同步在一个私有的静态字段上
val 定义一个局部变量并立即为其赋值,这个变量为 final 类型,不能被修改

4.2 部分使用介绍

上述注释大多数使用看功能描述基本都能理解,下面挑出一些不太容易理解的进行使用说明。

@Getter(lazy=true)

@Getter(lazy=true) 为字段生成延迟初始化的 getter 方法。这个 getter 方法在第一次被调用时初始化字段,并将结果缓存。一般用于需要获取的某一个属性比较消耗资源时,便可以通过该注解添加 lazy=true 属性实现懒加载,这会生成 Double Check Lock 样板代码对属性进行懒加载。

在 Java 中,“double check lock”(双重检查锁)模式是一种常用的多线程并发编程技巧,主要用于延迟初始化并确保只有一个线程能够初始化资源。当你在 Lombok 中使用 @Getter(lazy=true) 注解时,Lombok 就会生成对应的双重检查锁的代码。这样可以确保字段的延迟初始化,在多线程环境中也能保证线程安全,而且只会初始化一次。

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());
    }
}

编译后的代码会是如下这样的:

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

@Value 为类生成只读属性,即所有字段都是 final,包括 getter 方法、equalshashCodetoString 方法。此时此类相当于 final 类,无法被继承,其属性也会变成 final 属性。

@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);
    }
}

编译后的代码会是如下这样的:

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

@Builder 为类实现 Builder 设计模式(建造者模式),通常用于链式构造复杂对象。

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);
    }
}

编译后的代码会是如下这样的:

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

@SneakyThrows 用于方法上,可以让方法抛出被检查的异常,而不需要显式地在方法上使用 throws 关键字抛出异常。

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());
    }
}

编译后的代码会是如下这样的:

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

使用 Lombok 生成日志对象时,根据使用日志实现的不同,有多种注解可以使用。比如 @Log@Log4j@Log4j2@Slf4j 等。以 @Slf4j 为例,它会在类中添加一个名为 ‘log’ 的 SLF4J 日志器对象。

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");
    }
}

编译后的代码会是如下这样的:

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

@Cleanup 用于自动管理资源,用于自动调用 close() 方法释放资源,避免了手动释放。

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

编译后的代码会是如下这样的:

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

@NonNull 用于检查方法或构造器的参数是否为 null,如果为 null 就会抛出 NullPointerException,一般用来做非空判断。

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

编译后的代码会是如下这样的:

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

@With 用于为字段生成返回更新了某个字段值的新对象的方法。简单来说,它可以实现对原对象进行克隆,并改变其一个属性,使用时需要指定全参构造方法。

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);
    }
}

编译后的代码会是如下这样的:

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

当我们在多个线程中访问同一资源时,往往会出现线程安全问题,往往我们会使用 synchronized 关键字修饰方法来实现同步访问。而 @Synchronized 是 Lombok 提供的同步锁,用于方法,将会同步在一个私有的字段上,如果是静态方法,则同步在一个私有的静态字段上。

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 对象
    }
}

编译后的代码会是如下这样的:

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

val 用于定义一个任意类型的局部变量并立即为其赋值,这个变量为 final 类型,不能被修改。

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!");
    }
}

编译后的代码会是如下这样的:

public class ValExample {
    
    
    public ValExample() {
    
    
    }

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

猜你喜欢

转载自blog.csdn.net/ly1347889755/article/details/130996976