极简代码神器:Lombok使用教程

1,669 阅读14分钟

(点击即可跳转阅读)

1. SpringBoot内容聚合

2. 面试题内容聚合

3. 设计模式内容聚合

4. 排序算法内容聚合

5. 多线程内容聚合

Lombok 是一个非常神奇的 java 类库,会利用注解自动生成 java Bean 中烦人的 Getter、Setter,还能自动生成 logger、ToString、HashCode、Builder 等 java特色的函数或是符合设计模式的函数,能够让你 java Bean 更简洁,更美观。

lombok 的思想非常先进,它让我们省略繁琐的样板代码,不要在重复的代码上花费太长时间,它也是Java语言演进过程中必然出现的一种思想,要用20% 的时间做 80%的事情。

下面就来看一下 lombok 的具体用法。

@Data

@Data 是一个很方便的注解,它和@ToString@EqualAndHashCode@Getter/@Setter、和@RequiredArgsConstructor 绑定在一起。换句话说,@Data 生成通常与简单的POJO(Plain Old Java Objects) 和 bean 相关联的所有样板代码,例如:获取所有的属性,设置所有不可继承的属性,适应toString、equals 和 hashcode 的实现,通过构造方法初始化所有final 的属性,以及所有没有使用@NonNull标记的初始化程序的非final字段,以确保该字段永远不为null。

@Data 就像在类上隐含使用 @toString 、 @EqualAndHashCode、 @Getter、 @Setter 和 @RequiredArgsConstructor 注释一样。@Data = @Getter + @Setter + @ToString + @EqualsAndHashCode + @RequiredArgsConstructor

但是,@Data 无法设置这些注解的参数,例如callSuper、includeFieldNames 和 exclude

如果您需要为这些参数中的任何一个设置非默认值,只需显式添加这些注释;

生成的所有getters/setters 默认都是public 的,为了覆盖访问级别,请使用显式的@Setter @Getter批注对字段或类进行注释。你可以使用这个注释(通过与 AccessLevel.NONE结合)来禁止使用 getter或setter。

所有使用 transient 标记的字段都不会视为 hashcode 和 equals。将完全跳过所有静态字段(不考虑任何生成的方法,并且不会为它们创建setter / getter)。

如果类已经包含与通常生成的任何方法具有相同名称和参数计数的方法,则不会生成该方法,也不会发出警告或错误。

例如:如果你使用 equals 标记了一个方法,那么不会再生成 equals 方法,即使从技术上讲,由于具有不同的参数类型,它可能是完全不同的方法。同样的规则适用于构造函数(任何显式构造函数都会阻止 @Data 生成一个),以及toString,equals和所有getter和setter。

您可以使用@ lombok.experimental.Tolerate 标记任何构造函数或方法,以将它们隐藏在 lombok 中

例如:

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
import lombok.AccessLevel;
import lombok.Data;
import lombok.Setter;
import lombok.ToString;
 
@Data
public class DataExample {
 
    private final String name;
 
    @Setter(AccessLevel.PACKAGE)
    private int age;
 
    private double score;
 
    private String[] tags;
 
    @ToString(includeFieldNames = true)
    @Data(staticConstructor = "of")
    public static class Exercise<T> {
        private final String name;
        private final T value;
    }
 
}

就相当于是不用 lombok 的如下示例:

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
import java.util.Arrays;
 
public class DataExample {
  private final String name;
  private int age;
  private double score;
  private String[] tags;
 
  public DataExample(String name) {
    this.name = name;
  }
 
  public String getName() {
    return this.name;
  }
 
  void setAge(int age) {
    this.age = age;
  }
 
  public int getAge() {
    return this.age;
  }
 
  public void setScore(double score) {
    this.score = score;
  }
 
  public double getScore() {
    return this.score;
  }
 
  public String[] getTags() {
    return this.tags;
  }
 
  public void setTags(String[] tags) {
    this.tags = tags;
  }
 
  @Override public String toString() {
    return "DataExample(" + this.getName() + ", " + this.getAge() + ", " + this.getScore() + ", " + Arrays.deepToString(this.getTags()) + ")";
  }
 
  protected boolean canEqual(Object other) {
    return other instanceof DataExample;
  }
 
  @Override public boolean equals(Object o) {
    if (o == this) return true;
    if (!(o instanceof DataExample)) return false;
    DataExample other = (DataExample) o;
    if (!other.canEqual((Object)this)) return false;
    if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false;
    if (this.getAge() != other.getAge()) return false;
    if (Double.compare(this.getScore(), other.getScore()) != 0) return false;
    if (!Arrays.deepEquals(this.getTags(), other.getTags())) return false;
    return true;
  }
 
  @Override public int hashCode() {
    final int PRIME = 59;
    int result = 1;
    final long temp1 = Double.doubleToLongBits(this.getScore());
    result = (result*PRIME) + (this.getName() == null ? 43 : this.getName().hashCode());
    result = (result*PRIME) + this.getAge();
    result = (result*PRIME) + (int)(temp1 ^ (temp1 >>> 32));
    result = (result*PRIME) + Arrays.deepHashCode(this.getTags());
    return result;
  }
 
  public static class Exercise<T> {
    private final String name;
    private final T value;
 
    private Exercise(String name, T value) {
      this.name = name;
      this.value = value;
    }
 
    public static <T> Exercise<T> of(String name, T value) {
      return new Exercise<T>(name, value);
    }
 
    public String getName() {
      return this.name;
    }
 
    public T getValue() {
      return this.value;
    }
 
    @Override public String toString() {
      return "Exercise(name=" + this.getName() + ", value=" + this.getValue() + ")";
    }
 
    protected boolean canEqual(Object other) {
      return other instanceof Exercise;
    }
 
    @Override public boolean equals(Object o) {
      if (o == this) return true;
      if (!(o instanceof Exercise)) return false;
      Exercise<?> other = (Exercise<?>) o;
      if (!other.canEqual((Object)this)) return false;
      if (this.getName() == null ? other.getValue() != null : !this.getName().equals(other.getName())) return false;
      if (this.getValue() == null ? other.getValue() != null : !this.getValue().equals(other.getValue())) return false;
      return true;
    }
 
    @Override public int hashCode() {
      final int PRIME = 59;
      int result = 1;
      result = (result*PRIME) + (this.getName() == null ? 43 : this.getName().hashCode());
      result = (result*PRIME) + (this.getValue() == null ? 43 : this.getValue().hashCode());
      return result;
    }
  }
}

@NonNull

你可以使用 @NonNull 对方法或者构造器生成 null - check

如果lombok为您生成整个方法或构造函数(例如@Data),Lombok总是将字段上通常称为@NonNull的各种注释视为生成空值检查的信号。但是,现在,在参数上使用lombok自己的@lombok.NonNull会导致在您自己的方法或构造函数中只插入null-check语句。

Null - Check 语句看起来像是如下语句

1
2
3
if(param == null){
  throw new NullPointerException("param is marked @NonNull but is null")
}

这条判空语句会在方法的最开始进行判断

1
2
3
4
5
6
7
8
9
public class NonNullExample {
 
    @Getter
    private String name;
 
    public NonNullExample(@NonNull String name){
        this.name = name;
    }
}

这个加上 @NonNull 判空的代码就相当于如下代码

1
2
3
4
5
6
7
8
9
10
11
12
import lombok.NonNull;
 
public class NonNullExample {
  private String name;
 
  public NonNullExample(@NonNull String name) {
    if (name == null) {
      throw new NullPointerException("name is marked @NonNull but is null");
    }
    this.name = name;
  }
}

@Getter & @Setter

你可以使用 @Getter 和 @Setter 自动生成任何 getter/setter。

默认的 getter 只返回字段的名称,如果字段的名称为 foo,则返回的是 getFoo(),如果字段类型为 boolean ,则返回 isFoo()。如果字段为 foo 的话,默认的 setter 返回 setFoo,并且类型是 void ,并且带有一个和该属性相同的字段作为参数,用于为此属性字段进行赋值。

除非你指定AccessLevel 访问级别,否则使用 Getter / Setter 生成的方法默认是 public 的作用范围。AccessLevel的访问级别有 PUBLIC, PROTECTED, PACKAGE, and PRIVATE.

你也可以在类上使用 @Getter / @Setter ,在这种情况下,就会对该类中的所有非静态属性生成 get and set 方法

你也可以通过设置 AccessLevel.NONE 禁用任何 get and set 方法的生成。这会使 @Data、@Getter / @Setter 的注解失效。

1
2
3
4
5
6
7
8
9
10
11
12
public class GetterSetterExample {
 
    @Setter
    @Getter
    private int age = 10;
 
    @Setter(AccessLevel.PROTECTED)
    private String name;
 
    @Getter(AccessLevel.PRIVATE)
    private String high;
}

就等同于

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class GetterSetterExample {
 
  private int age = 10;
 
  private String name;
 
  private String high;
 
  public int getAge() {
    return age;
  }
 
  public void setAge(int age) {
    this.age = age;
  }
 
  protected void setName(String name) {
    this.name = name;
  }
 
  private String getHigh(){
    return high;
  }
}

@ToString

@ToString 注解用来替换掉生成 toString() 方法的实现,默认情况下,它会按顺序打印你的班级名称以及每个字段,并以逗号分隔。

通过设置 includeFieldNames = true 能够使 toString() 方法打印每个字段的属性值和名称。

默认情况下,所有非静态属性都被打印,如果你想要排除某些字段的话,需要设置 @ToString.Exclude,或者,你可以指定ToString(onlyExplicitlyIncluded = true)来指定哪些你希望使用的字段。然后使用@ ToString.Include标记要包含的每个字段。

通过设置 callSuper 为 true ,可以将toString的超类实现的输出包含到输出中。请注意,java.lang.Object 的 toString() 实现没有任何意义,所以你可能不会这样做除非你想要扩展另一个类。

你还可以在toString 中包含方法调用的输出。只能包含不带参数的实例(非静态)方法,为此,请使用@ ToString.Include标记方法。

你可以使用 @ToString.Include(name =“some other name”)更改用于标识成员的名称,并且可以通过 @ ToString.Include(rank = -1)更改成员的打印顺序。没有定义等级的成员默认是0级,等级高的成员优先被打印,优先级相同的成员按照它们在源文件中出现的顺序打印。

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
@ToString
public class ToStringExample {
 
  // 静态属性不会包含
  private static final int STATIC_VAR = 10;
  private String name;
  private String[] tags;
  private Shape shape = new Square(5, 10);
 
  // 排除指定字段不会被 toString 打印
  @ToString.Exclude
  private int id;
 
  public String getName() {
    return this.name;
  }
 
  // callSuper 表示是否扩展父类的 toString(), 
  // includeFieldNames 表示是否包含属性名称
  @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;
    }
  }
 
  public static class Shape {}
}

测试一下上面的示例

1
2
ToStringExample toStringExample = new ToStringExample();
System.out.println(toStringExample);

输出如下

1
ToStringExample(name=null, tags=null, shape=ToStringExample.Square(super=com.project.lombok.ToStringExample$Square@1b9e1916, width=5, height=10))

注释掉 callSuper = true, 测试结果如下

1
ToStringExample(name=null, tags=null, shape=ToStringExample.Square(width=5, height=10))

从输出可以看出,如果不扩展父类,不会输出关于 Shape 的内部类信息,callSuper 默认为 false

注释掉 includeFieldNames,测试结果不会发生变化,所以 includeFieldNames 默认值为 true

更改 includeFieldNames = false,测试结果如下

1
ToStringExample(name=null, tags=null, shape=ToStringExample.Square(super=com.project.lombok.ToStringExample$Square@1b9e1916, 5, 10))

从输出可以看出,如果设置 includeFieldNames = false ,不会输出Shape 中的字段名称信息。

上面用@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
25
26
27
28
29
30
31
32
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) + ")";
  }
 
  public static class Shape {}
}

@EqualsAndHashCode

任何类的定义都可以用@EqualsAndHashCode 标注,让 lombok 为其生成 equalshashCode 方法。默认情况下,将会用在非静态,非 transient 标记的字段上,但是你可以通过 @EqualsAndHashCode.Include@EqualsAndHashCode.Exclude 标记类型成员来修改使用哪些字段。

或者,你可以通过使用 @EqualsAndHashCode.Include 并使用 @EqualsAndHashCode(onlyExplicitlyIncluded = true)标记它们来准确指定你希望使用的字段或方法。

如果将 @EqualsAndHashCode 应用于扩展另一个的类,这个特性就会变的很危险。通常来说,对类自动生成equalshashcode 方法不是一个好的选择,因为超类也定义了字段,这些字段也需要equals / hashCode方法。通过设置 callSuper 为 true,可以在生成的方法中包含超类的 equals 和 hachcode 方法。

对于 hashCode 来说,super.hashCode 的结果包括了哈希算法,对于 equals 来说,如果超类实现认为它不等于传入的对象,生成的方法将返回 false。请注意,不是所有的equals 实现都能正确处理这种情况。然而,lombok生成的 equals实现可以正确处理这种情况。

如果不扩展类时(只扩展任何java.lang.Object 类)时把 callSuper 设置为 true 会提示编译错误,因为 lombok 会将生成的 equals() 方法和 hashCode() 实现转换为从 Object 继承过来:只有相同的 Object 对象彼此相等并且具有相同的 hashCode 。

当你继承其他类时没有设置 callSuper 为 true 会进行警告,因为除非父类没有相同的属性,lombok无法为您生成考虑超类声明的字段的实现。你需要自己写实现类或者依赖 callSuper 工具。你还可以使用 lombok.equalsAndHashCode.callSuper 配置key。

下面是一个例子

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
@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 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;
        }
    }
 
    public static class Shape {}
}

@NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor

lombok 有三个生成构造函数的注解,下面一起来看一下它们的使用说明和示例

@NoArgsConstructor 将会生成无参数的构造函数,如果有final 修饰的字段并且没有为 final 修饰的字段进行初始化的话,那么单纯的使用 @NoArgsConstructor 注解会提示编译错误

极简代码神器:Lombok使用教程

修改建议:需要为 @NoArgsConstructor 指定一个属性@NoArgsConstructor(force=true),lombok会为上面的final 字段默认添加初始值,因为id 是 int 类型,所以 id 的初始值为 0,类似的不同类型的字段的初始值还有 false / null / 0,特定的 Java 构造,像是 hibernate 和 服务提供接口需要无参数的构造方法。此注解主要与 @Data 或生成注解的其他构造函数组合使用。

这里有个需要注意的地方:@NonNull 不要和 @NoArgsConstructor 一起使用

1
2
3
4
5
6
7
8
9
10
11
@NoArgsConstructor
@Getter
public class NoArgsConstructorExample {
    private  Long id ;
    private @NonNull String name;
    private Integer age;
 
    public static void main(String[] args) {
        System.out.println(new NoArgsConstructorExample().getName());
    }
}

输出结果是 null ,因此如果有 @NonNull 修饰的成员的变量就不要用 @NoArgsConstructor 修饰类

@RequiredArgsConstructor 将为每个需要特殊处理的字段生成一个带有1个参数的构造函数。所有未初始化的 final 字段都会获取一个参数,以及标记为 @NonNull 的任何字段也会获取一个参数。这些字段在声明它们的地方没有初始化。对于这些标记为 @NonNull 的字段,会生成特殊的null 编译检查。如果标记为 @NonNull 的字段的参数为 null,那么构造函数将会抛出 NullPointerException。参数的顺序与字段在类中的显示顺序相匹配。

例如下面这个例子,只有 @NonNull 和 final 修饰的字段才会加入构造函数

1
2
3
4
5
6
7
8
9
@RequiredArgsConstructor
public class RequiredArgsConstructorExample {
 
    @NonNull
    private int id;
    private final String name;
    private boolean human;
 
}

生成的结果大概是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class RequiredArgsConstructorExample {
    @NonNull
    private int id;
    private final String name;
    private boolean human;
 
    public RequiredArgsConstructorExample(@NonNull int id, String name) {
        if (id == null) {
            throw new NullPointerException("id is marked @NonNull but is null");
        } else {
            this.id = id;
            this.name = name;
        }
    }
}

@AllArgsConstructor: @AllArgsConstructor 为类中的每个字段生成一个带有1个参数的构造函数。标有@NonNull 的字段会导致对这些参数进行空检查。

1
2
3
4
5
6
7
8
@AllArgsConstructor
public class AllArgsConstructorExample {
 
    private int id;
    private String name;
    private int age;
 
}

相当于自动生成如下代码

1
2
3
4
5
public AllArgsConstructorExample(int id, String name, int age) {
    this.id = id;
    this.name = name;
    this.age = age;
}

这些注解中的每一个都允许使用替代形式,其中生成的构造函数始终是私有的,并且生成包含私有构造函数的附加静态工厂方法,通过为注释提供staticName值来启用此模式,@RequiredArgsConstructor(staticName =“of”)。看下面这个例子

1
2
3
4
5
6
7
8
9
10
11
@RequiredArgsConstructor(staticName = "of")
@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;
  }
}

就会变为

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
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;
  }
 
  public static <T> ConstructorExample<T> of(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() {
    }
  }
}

文章参考:

www.hellojava.com/a/74973.htm…
www.projectlombok.org/features/co…

看到这里了,关注一个?

极简代码神器:Lombok使用教程