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

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

(点击即可跳转阅读)


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 中


例如:

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 的如下示例:

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 语句看起来像是如下语句

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


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


public class NonNullExample {
    @Getter
    private String name;
    public NonNullExample(@NonNull String name){
        this.name = name;
    }
}


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


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 的注解失效。


public class GetterSetterExample {
    @Setter
    @Getter
    private int age = 10;
    @Setter(AccessLevel.PROTECTED)
    private String name;
    @Getter(AccessLevel.PRIVATE)
    private String high;
}


就等同于


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级,等级高的成员优先被打印,优先级相同的成员按照它们在源文件中出现的顺序打印。


@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 {}
}


测试一下上面的示例


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


输出如下


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


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


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


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


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


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


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


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


上面用@ToString 注解修饰的例子就相当于是下面这段代码


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。

下面是一个例子



@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 注解会提示编译错误


image.png


修改建议:需要为 @NoArgsConstructor 指定一个属性

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


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

@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 修饰的字段才会加入构造函数


@RequiredArgsConstructor
public class RequiredArgsConstructorExample {
    @NonNull
    private int id;
    private final String name;
    private boolean human;
}


生成的结果大概是这样的


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 的字段会导致对这些参数进行空检查。


@AllArgsConstructor
public class AllArgsConstructorExample {
    private int id;
    private String name;
    private int age;
}


相当于自动生成如下代码


public AllArgsConstructorExample(int id, String name, int age) {
    this.id = id;
    this.name = name;
    this.age = age;
}


这些注解中的每一个都允许使用替代形式,其中生成的构造函数始终是私有的,并且生成包含私有构造函数的附加静态工厂方法,通过为注释提供staticName值来启用此模式,

@RequiredArgsConstructor(staticName =“of”)。看下面这个例子


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


就会变为


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


文章参考:

https://www.hellojava.com/a/74973.html

https://www.projectlombok.org/features/constructor

目录
相关文章
|
1天前
|
Java 编译器 程序员
Java一分钟之第一行Java代码:输出"Hello, World!"
【5月更文挑战第7天】本文引导初学者编写运行第一个Java程序——打印&quot;Hello, World!&quot;,介绍基本代码结构及常见问题。包括语法错误(如缺少分号、缩进不规范)、编译运行问题(忘记编译、运行错误)和环境配置问题(JDK未安装、环境变量未设置)。建议检查语法、熟悉编译运行流程并正确安装配置JDK。通过实战演练,从编写到运行,迈出Java编程第一步。
8 0
|
1天前
|
JavaScript 前端开发 测试技术
《手把手教你》系列技巧篇(三十八)-java+ selenium自动化测试-日历时间控件-下篇(详解教程)
【5月更文挑战第2天】在自动化测试过程中,经常会遇到处理日期控件的点击问题。宏哥之前分享过一种方法,但如果输入框是`readonly`属性,这种方法就无法奏效了。不过,通过修改元素属性,依然可以实现自动化填写日期。首先,定位到日期输入框并移除`readonly`属性,然后使用`sendKeys`方法输入日期。这样,即使输入框设置了`readonly`,也能成功处理日期控件。
13 1
|
1天前
|
Java
接口在增强Java代码的灵活性方面起着关键作用
Java接口增强代码灵活性,实现多态性、解耦、多继承和扩展性。通过接口,类可隐藏实现细节,实现抽象化,促进模块化和维护性。接口定义方法,允许不同类实现,减少依赖,便于测试和修改。同时,接口提供多继承解决方案,使代码更具扩展性,易于添加新功能。
13 4
|
1天前
|
IDE Java 数据库连接
Lombok注解大全
这些是Lombok中的一些常见注解,它们可以显著减少Java代码中的冗余代码,提高代码的可读性和可维护性。不过,在使用Lombok之前,请确保你的开发环境已经配置好支持Lombok,通常需要安装相应的插件或进行设置以使IDE(如Eclipse、IntelliJ IDEA)能够正确解析Lombok注解。
11 2
|
2天前
|
搜索推荐 Java Shell
8大Java排序方法(由简入繁),有代码详解和原理指导
8大Java排序方法(由简入繁),有代码详解和原理指导
16 0
|
2天前
|
Java 测试技术 Python
《手把手教你》系列技巧篇(三十七)-java+ selenium自动化测试-日历时间控件-上篇(详解教程)
【5月更文挑战第1天】该文介绍了使用Selenium自动化测试网页日历控件的方法。首先,文章提到在某些Web应用中,日历控件常用于选择日期并筛选数据。接着,它提供了两个实现思路:一是将日历视为文本输入框,直接输入日期;二是模拟用户交互,逐步选择日期。文中给出了JQueryUI网站的一个示例,并展示了对应的Java代码实现,包括点击日历、选择日期等操作。
13 0
|
2天前
|
Java Apache
Java代码使用POI导出的单元格加上边框和背景色
【5月更文挑战第3天】Java代码使用POI导出的单元格加上边框和背景色
18 0
|
2天前
|
Java Apache
Java代码使用POI导出的单元格的字体加粗设置
【5月更文挑战第3天】Java代码使用POI导出的单元格的字体加粗设置
18 1
|
2天前
|
IDE Java 数据库连接
Lombok注解大全
这些是Lombok中的一些常见注解,它们可以显著减少Java代码中的冗余代码,提高代码的可读性和可维护性。不过,在使用Lombok之前,请确保你的开发环境已经配置好支持Lombok,通常需要安装相应的插件或进行设置以使IDE(如Eclipse、IntelliJ IDEA)能够正确解析Lombok注解。
13 4
|
6天前
|
Java
如何解决使用若依前后端分离打包部署到服务器上后主包无法找到从包中的文件的问题?如何在 Java 代码中访问 jar 包中的资源文件?
如何解决使用若依前后端分离打包部署到服务器上后主包无法找到从包中的文件的问题?如何在 Java 代码中访问 jar 包中的资源文件?
35 0