Lombok

369 阅读6分钟

一、简介

Lombok 是一个非常实用的 Java 工具(实用 ≠ 可以乱用),可以通过注解来省去繁琐的模板代码编写。例如,在 JavaBean 中使用@Getter/@Setter@ToString@NoArgsConstructor等注解后就无需再编写 getter/setter 方法、toString() 方法以及无参构造方法,尽管在 IDEA 中可以通过快捷键 Ctrl + Alt + Insert 快速的生成这些模板代码。

除此之外,Lombok 的@Builder注解允许通过链式调用的方式创建对象,通过@Slf4j@Log4j@Log4j2注解来为不同的日志框架创建一个名为log的静态的且 final 修饰的日志对象等等......

下面将参考 Lombok 的官方文档来介绍几个常用的注解。

二、常用注解

1.@Getter / @Setter

@Getter/@Setter注解用来替代 getter/setter 方法的编写,因为在编译阶段,会自动为 JavaBean 生成 getter/setter 方法。@Getter/@Setter注解的作用对象为ElementType.FIELDElementType.TYPE,即只能作用在属性和类上。

默认情况下,使用@Getter/@Setter自动生成的 getter/setter 方法访问修饰符为public,如果需要修改访问修饰符,则可以通过 AccessLevel 来指定访问级别。

(1) With Lombok

import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;

public class GetterSetterExample {
    @Getter
    @Setter
    private int age = 10;

    // 指定访问修饰符
    @Setter(AccessLevel.PROTECTED)
    private String name;
}

(2) Vanilla Java

public class GetterSetterExample {
    private int age = 10;
    private String name;
    
    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    protected void setName(String name) {
        this.name = name;
    }
}

注意:

对于 boolean 类型的字段,在使用 @Getter 注解后,自动生成的 getter() 方法以 is 为前缀,而不是以get 为前缀;如果想 getter() 方法以 get 为前缀,则用 Boolean 包装类型来替换 boolean 类型。

2.@ToString

@ToString注解用来替换 toString() 方法的编写,只能作用在类上,由于其实用性不高,不做过多讲解。

(1) With Lombok

import lombok.ToString;

@ToString
public class ToStringExample {
    private static final int STATIC_VAR = 10;
    private String name;
    private Shape shape = new Square(5, 10);
    private String[] tags;
    // 字段排除
    @ToString.Exclude
    private int id;

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

    // callSuper属性为true表示会调用父类的toString()方法,includeFieldNames属性为true表示打印字段名和字段值
    @ToString(callSuper=true, includeFieldNames=true)
    public static class Square extends Shape {
        private final int width, height;

        public Square(int width, int height) {
            this.width = width;
            this.height = height;
        }
    }
}

(2) Vanilla Java

public class ToStringExample {
    private static final int STATIC_VAR = 10;
    private String name;
    private Shape shape = new Square(5, 10);
    private String[] tags;
    private int id;

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

    public static class Square extends Shape {
        private final int width, height;

        public Square(int width, int height) {
            this.width = width;
            this.height = height;
        }

        @Override
        public String toString() {
            return "Square(super=" + super.toString() + ", width=" + this.width + ", height=" + this.height + ")";
        }
    }

    @Override
    public String toString() {
        return "ToStringExample(" + this.getName() + ", " + this.shape + ", " + Arrays.deepToString(this.tags) + ")";
    }
}

3.@EqualsAndHashCode

@EqualsAndHashCode注解用来自动生成 equals() 方法和 hashCode() 方法,该注解只能作用在类上。

默认情况下,@EqualsAndHashCode生成的 equals() 方法和 hashCode() 方法中不会使用 static 和 transient 修饰的属性。可以使用@EqualsAndHashCode.Include来指定一些属性,也可以使用@EqualsAndHashCode.Exclude来排除一些属性。

默认情况下,@EqualsAndHashCode生成的 equals() 方法和 hashCode() 方法中不会调用父类的方法;但是,可以通过callSupper=true来让其生成的方法调用父类中的方法。

(1) With Lombok

import lombok.EqualsAndHashCode;

@EqualsAndHashCode
public class EqualsAndHashCodeExample {
    private transient int transientVar = 10;
    
    private String name;
    
    private double score;
    
    // 属性排除
    @EqualsAndHashCode.Exclude
    private Shape shape = new Square(5, 10);
    
    private String[] tags;
    
    // 属性排除
    @EqualsAndHashCode.Exclude
    private int id;

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

    @EqualsAndHashCode(callSuper=true)
    public static class Square extends Shape {
        private final int width, height;

        public Square(int width, int height) {
            this.width = width;
            this.height = height;
        }
    }

(2) Vanilla Java

import java.util.Arrays;

public class EqualsAndHashCodeExample {
    private transient int transientVar = 10;
    private String name;
    private double score;
    private Shape shape = new Square(5, 10);
    private String[] tags;
    private int id;

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

    @Override
    public boolean equals(Object o) {
        if (o == this) return true;
        if (!(o instanceof EqualsAndHashCodeExample)) return false;
        EqualsAndHashCodeExample other = (EqualsAndHashCodeExample) o;
        if (!other.canEqual((Object)this)) return false;
        if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false;
        if (Double.compare(this.score, other.score) != 0) return false;
        if (!Arrays.deepEquals(this.tags, other.tags)) return false;

        return true;
    }

    @Override
    public int hashCode() {
        final int PRIME = 59;
        int result = 1;
        final long temp1 = Double.doubleToLongBits(this.score);
        result = (result*PRIME) + (this.name == null ? 43 : this.name.hashCode());
        result = (result*PRIME) + (int)(temp1 ^ (temp1 >>> 32));
        result = (result*PRIME) + Arrays.deepHashCode(this.tags);

        return result;
    }

    protected boolean canEqual(Object other) {
        return other instanceof EqualsAndHashCodeExample;
    }

    public static class Square extends Shape {
        private final int width, height;

        public Square(int width, int height) {
            this.width = width;
            this.height = height;
        }

        @Override
        public boolean equals(Object o) {
            if (o == this) return true;
            if (!(o instanceof Square)) return false;
            Square other = (Square) o;
            if (!other.canEqual((Object)this)) return false;
            if (!super.equals(o)) return false;
            if (this.width != other.width) return false;
            if (this.height != other.height) return false;
            return true;
        }

        @Override
        public int hashCode() {
            final int PRIME = 59;
            int result = 1;
            result = (result*PRIME) + super.hashCode();
            result = (result*PRIME) + this.width;
            result = (result*PRIME) + this.height;
            return result;
        }

        protected boolean canEqual(Object other) {
            return other instanceof Square;
        }
    }
}

4.@NoArgsConstructor/@RequiredArgsConstructor/@AllArgsConstructor

这3个注解是用来生成构成函数的,只能作用在类上:

@NoArgsConstructor注解会生成无参构造函数,如果有 final 修饰的属性,但是并没有直接初始化时,则使用@NoArgsConstructor会报错,此时设置属性force=true,编译时会为 final 修饰的属性设置默认值。

@RequiredArgsConstructor注解会为没有进行初始化的 final 属性以及被@NonNull标注的属性提供构造函数。

@AllArgsConstructor注解生成带有全属性的构造函数。

默认情况下,自动生成的构造函数都是public的,可以通过设置access属性来指定访问权限。

如果使用了staticName 属性,则生成的构造函数private的,同时会生成一个以staticName属性值命名的静态方法来调用private构造函数。

(1) With Lombok

import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.NonNull;

// 会生成一个静态的方法,方法名为getInstance()
@RequiredArgsConstructor(staticName = "getInstance")
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public class ConstructorExample<T> {
    private int x, y;

    @NonNull
    private T description;

    @NoArgsConstructor
    public static class NoArgsExample {
        @NonNull
        private String field;
    }
}

(2) Vanilla Java

public class ConstructorExample<T> {
    private int x, y;

    @NonNull
    private T description;

    private ConstructorExample(T description) {
        if (description == null) throw new NullPointerException("description");
        this.description = description;
    }

    // 静态方法getInstance()调用private修饰的构造方法
    public static <T> ConstructorExample<T> getInstance(T description) {
        return new ConstructorExample<T>(description);
    }

    @java.beans.ConstructorProperties({"x", "y", "description"})
    protected ConstructorExample(int x, int y, T description) {
        if (description == null) throw new NullPointerException("description");
        this.x = x;
        this.y = y;
        this.description = description;
    }

    public static class NoArgsExample {
        @NonNull
        private String field;

        public NoArgsExample() {
        }
    }
}

5.@Data

@Data 注解等价于@Getter + @Setter + @ToString + @EqualsAndHashCode + @RequiredArgsConstructor,不再累述。

6.@NonNull

@NonNull注解用来进行 check null,能够有效的防止空指针异常,能够作用在属性、方法、参数、本地变量,比较简单,不再累述。

(1) With Lombok

import lombok.NonNull;

public class NonNullExample extends Something {
    private String name;

    public NonNullExample(@NonNull Person person) {
        super("Hello");
        this.name = person.getName();
    }
}

(2) Vanilla Java

import lombok.NonNull;

public class NonNullExample extends Something {
    private String name;

    public NonNullExample(@NonNull Person person) {
        super("Hello");
        // null 检验
        if (person == null) {
            throw new NullPointerException("person is marked @NonNull but is null");
        }
        this.name = person.getName();
    }
}

7.@Builder

@Builder注解是 Lombok 常用且实用的一个注解,使用了@Builder注解后就可以链式的调用 getter/setter 方法来创建对象,如下所示:

Person person = Person.builder()
    .name("Adam Savage")
    .city("San Francisco")
    .job("Mythbusters")
    .job("Unchained Reaction")
    .build();

8.@Slf4j

@Slf4j注解会为 slf4j 日志框架生成一个名为 log的对象,如下所示:

private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(LogExample.class);

@Log4j@Log4j2同理,这里不再累述。

(1) With Lombok

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class LogExampleOther {
    public static void main(String... args) {
        log.error("Something else is wrong here");
    }
}

(2) Vanilla Java

public class LogExampleOther {
    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExampleOther.class);

    public static void main(String... args) {
        log.error("Something else is wrong here");
    }
}

三、集成 Lombok

在 pom.xml 文件中引入 Lombok 依赖,如下所示:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.22</version>
</dependency>

另外,在 IDEA 编辑器中需要安装 Lombok 插件,否则编译会报错。

参考文档