Lombok注解详解

目录
  1. 1. @Data
  2. 2. @Getter & @Setter
  3. 3. @ToString
  4. 4. @EqualsAndHashCode
  5. 5. @RequiredArgsConstructor
  6. 6. @NoArgsConstructor & @AllArgsConstructor
  7. 7. @NonNull
  8. 8. @Cleanup
  9. 9. @Log & @Slf4j & @CommonsLog & @JBossLog 等
  10. 10. @Builder
  11. 11. @Synchronized
  12. 12. @Accessors
  13. 13. @SneakyThrows

@Data

这个注解是最常用的,是一个类级别注解,同时也是一个 复合型注解,所谓复合型注解,就是多个注解的集合体。

这个注解包含了 @Getter@Setter@ToString@EqualsAndHashCode@RequiredArgsConstructor,每个注解的作用会在稍后一一介绍。

此注解有一个参数 staticConstructor 用来生成静态构造器,对应值为 构造器名。

使用注解的效果直接上代码:

  • 源文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import lombok.Data;

    @Data(staticConstructor="of")
    public class DataExample {
    private final String name;
    private int age;
    private double score;
    private String[] tags;
    }
  • 字节码文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    import java.beans.ConstructorProperties;
    import java.util.Arrays;

    public class DataExample {
    private final String name;
    private int age;
    private double score;
    private String[] tags;

    @ConstructorProperties({"name"})
    private DataExample(String name) {this.name = name;}
    public static DataExample of(String name) {return new DataExample(name);}

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

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

    public double getScore() {return this.score;}
    public void setScore(double score) {this.score = score;}

    public String[] getTags() {return this.tags;}
    public void setTags(String[] tags) {this.tags = tags;}

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

    public boolean equals(Object o) {
    if(o == this) {return true;} else if(!(o instanceof DataExample)) {return false;} else {DataExample other = (DataExample)o;
    if(!other.canEqual(this)) {return false;} else {Object this$name = this.getName();
    Object other$name = other.getName();
    if(this$name == null) {if(other$name == null) {return this.getAge() != other.getAge()?false:(Double.compare(this.getScore(), other.getScore())!= 0?false:Arrays.deepEquals(this.getTags(), other.getTags()));}
    } else if(this$name.equals(other$name)) {return this.getAge() != other.getAge()?false:(Double.compare(this.getScore(), other.getScore())!= 0?false:Arrays.deepEquals(this.getTags(), other.getTags()));}

    return false;
    }
    }
    }

    public int hashCode() {
    int PRIME = true;
    int result = 1;
    Object $name = this.getName();
    int result = result * 59 + ($name == null?43:$name.hashCode());
    result = result * 59 + this.getAge();
    long $score = Double.doubleToLongBits(this.getScore());
    result = result * 59 + (int)($score >>> 32 ^ $score);
    result = result * 59 + Arrays.deepHashCode(this.getTags());
    return result;
    }

    public String toString() {
    return "DataExample(name=" + this.getName() + ", age=" + this.getAge()+ ", score=" + this.getScore() + ", tags=" + Arrays.deepToString(this.getTags()) + ")";
    }
    }

通过官方文档,可以得知,当使用 @Data 注解时,则有了 @EqualsAndHashCode 注解,那么就会在此类中存在equals(Object other) 和 hashCode()方法,且不会使用父类的属性,这就导致了可能的问题。
比如,有多个类有相同的部分属性,把它们定义到父类中,恰好id(数据库主键)也在父类中,那么就会存在部分对象在比较时,它们并不相等,却因为lombok自动生成的equals(Object other) 和 hashCode()方法判定为相等,从而导致出错。

修复此问题的方法很简单:
1. 使用@Getter @Setter @ToString代替@Data并且自定义equals(Object other) 和 hashCode()方法,比如有些类只需要判断主键id是否相等即足矣。
2. 或者使用在使用@Data时同时加上@EqualsAndHashCode(callSuper=true)注解。

@Getter & @Setter

这两个注解是生成 get、set 方法的,可以写在类上,也可以写在属性上。这两个注解有一个重要的属性 value, 用来控制生成的方法的访问级别,对应值是 AccessLevel 类型枚举,分别有 PUBLIC, PROTECTED, PACKAGE, 和 PRIVATE,默认为 PUBLIC。

  • 源文件

    1
    2
    3
    4
    public class Example {
    @Getter @Setter private int age = 10;
    @Setter(AccessLevel.PROTECTED) private String name;
    }
  • 字节码文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class Example {
    private int age = 10;
    private String name;

    public DataExample(){}
    public int getAge() {return this.age;}
    public void setAge(int age) {this.age = age;}
    protected void setName(String name) {this.name = name;}
    }

@ToString

覆盖默认 toString()方法。其中常用参数介绍:

  1. includeFieldNames:生成的 toString() 方法是否包含字段名,布尔类型。
  2. exclude:不显示在 toString() 中的字段,String 数组。
  3. callSuper:调用父类 toString(),布尔类型。
  • 源码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    import lombok.ToString;

    @ToString(exclude="id")
    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.getName();
    }

    @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;
    }
    }
    }
  • 字节码(部分)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    import java.util.Arrays;

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

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

@EqualsAndHashCode

  • 源文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @EqualsAndHashCode(callSuper=true,exclude={"address","city","state","zip"})
    public class Person extends SentientBeing {
    enum Gender { Male, Female }

    @NonNull private String name;
    @NonNull private Gender gender;

    private String ssn;
    private String address;
    private String city;
    private String state;
    private String zip;
    }
  • 字节码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    public class Person extends SentientBeing {

    enum Gender {
    /*public static final*/ Male /* = new Gender() */,
    /*public static final*/ Female /* = new Gender() */;
    }
    @NonNull
    private String name;
    @NonNull
    private Gender gender;
    private String ssn;
    private String address;
    private String city;
    private String state;
    private String zip;

    @java.lang.Override
    public boolean equals(final java.lang.Object o) {
    if (o == this) return true;
    if (o == null) return false;
    if (o.getClass() != this.getClass()) return false;
    if (!super.equals(o)) return false;
    final Person other = (Person)o;
    if (this.name == null ? other.name != null : !this.name.equals(other.name)) return false;
    if (this.gender == null ? other.gender != null : !this.gender.equals(other.gender)) return false;
    if (this.ssn == null ? other.ssn != null : !this.ssn.equals(other.ssn)) return false;
    return true;
    }

    @java.lang.Override
    public int hashCode() {
    final int PRIME = 31;
    int result = 1;
    result = result * PRIME + super.hashCode();
    result = result * PRIME + (this.name == null ? 0 : this.name.hashCode());
    result = result * PRIME + (this.gender == null ? 0 : this.gender.hashCode());
    result = result * PRIME + (this.ssn == null ? 0 : this.ssn.hashCode());
    return result;
    }
    }

生成 equals 和 hashCode 方法,callSuper 及 exclude 等参数同 @ToString

如果@EqualsAndHashCode不是想排除某些字段,而是只包含某些字段:

1
2
3
4
5
6
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
class TestBean {
private int order;
@EqualsAndHashCode.Include
private String name;
}

onlyExplicitlyIncluded默认为false,所有的非静态和非瞬态的字段都会被包含进equals和hashCode方法中;为true时,只有在字段上明确使用了EqualsAndHashCode.Include注解才会被包含进equals和hashCode方法中。

@RequiredArgsConstructor

作用同注解名,生成必须的构造器,即:无参构造器,若类中用 final 标记的字段,则生成的是包含所有 final 类型字段的构造器。

@NoArgsConstructor & @AllArgsConstructor

无参构造器及全参构造器。注解参数同 @RequiredArgsConstructor

@NonNull

一般写在方法签名中,被此注解的字段,传入值禁止为 null,否则抛 NPE(NullPointerException)

  • 源文件

    1
    2
    @Getter @Setter @NonNull
    private List<Person> members;
  • 字节码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @NonNull
    private List<Person> members;

    public Family(@NonNull final List<Person> members) {
    if (members == null) throw new java.lang.NullPointerException("members");
    this.members = members;
    }

    @NonNull
    public List<Person> getMembers() {
    return members;
    }

    public void setMembers(@NonNull final List<Person> members) {
    if (members == null) throw new java.lang.NullPointerException("members");
    this.members = members;
    }

@Cleanup

被此注解标记的对象,在使用完毕后自动调用对象 close() 方法。

  • 源文件

    1
    @Cleanup InputStream in = new FileInputStream("some/file");
  • 字节码

    1
    2
    3
    4
    5
    6
    InputStream in = new FileInputStream("some/file");
    try {
    // do something
    } finally {
    if (in != null) in.close();
    }

@Log & @Slf4j & @CommonsLog & @JBossLog 等

注解在类上,在类中自动生成一个属性名为 log 的日志对象,无需再写类似如下代码:

1
private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LogExample.class.getName());

@Builder

生成链式构造,可与 @Data 等注解共存。

1
2
3
4
5
User user = User.builder()
.name("xxx")
.age(8)
.phone("13579246810")
.build();

@Synchronized

  • 源文件

    1
    2
    3
    4
    5
    6
    private DateFormat format = new SimpleDateFormat("MM-dd-YYYY");

    @Synchronized
    public String synchronizedFormat(Date date) {
    return format.format(date);
    }
  • 字节码

    1
    2
    3
    4
    5
    6
    7
    8
    private final java.lang.Object $lock = new java.lang.Object[0];
    private DateFormat format = new SimpleDateFormat("MM-dd-YYYY");

    public String synchronizedFormat(Date date) {
    synchronized ($lock) {
    return format.format(date);
    }
    }

@Accessors

在你的工作中,有时候可能会看到 @Accessors(chain = true)  这样的注解,他是lombok插件包中的一个注解,那么它是什么意思呢?

1. @Accessors 源码

我们打开 @Accessors 的源码可以看到:

(1)该注解主要作用是:当属性字段在生成 getter 和 setter 方法时,做一些相关的设置。

(2)当它可作用于类上时,修饰类中所有字段,当作用于具体字段时,只对该字段有效。

该字段共有三个属性,分别是 fluent,chain,prefix,下面我们分别来说明下,他的意思分别是什么?

2. @Accessors 属性说明

2.1 fluent 属性

不写默认为false,当该值为 true 时,对应字段的 getter 方法前面就没有 get,setter 方法就不会有 set。

2.2 chain 属性

不写默认为false,当该值为 true 时,对应字段的 setter 方法调用后,会返回当前对象。

2.3 prefix 属性

该属性是一个字符串数组,当该数组有值时,表示忽略字段中对应的前缀,生成对应的 getter 和 setter 方法。

比如现在有 xxName 字段和 yyAge 字段,xx 和 yy 分别是 name 字段和 age 字段的前缀。

那么,我们在生成的 getter 和 setter 方法如下,它也是带有 xx 和 yy 前缀的。

如果,我们把它的前缀加到 @Accessors 的属性值中,则可以像没有前缀那样,去调用字段的 getter和 setter 方法。


@SneakyThrows

自动抛受检异常,而无需显式在方法上使用throws语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import lombok.SneakyThrows;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
public class Test {
@SneakyThrows()
public void read() {
InputStream inputStream = new FileInputStream("");
}
@SneakyThrows
public void write() {
throw new UnsupportedEncodingException();
}

//相当于
public void read() throws FileNotFoundException {
InputStream inputStream = new FileInputStream("");
}
public void write() throws UnsupportedEncodingException {
throw new UnsupportedEncodingException();
}
}

在日常开发中,@SneakyThrows用的并不多,因为只是将异常抛出throw,还是需要你在调用方法时对异常做处理,它只是一个简化try-catch的写法。说白了它啥没做什么实际工作,加上它只是为了能让原本使用try-catch或者throws的程序能够正常编译而不报错。

扩展阅读:常用开发库 - Lombok工具库详解