posts - 21,comments - 0,views - 13619

初识 Lombok

Lombok 是什么?它是一个 Java 库,可以通过简单的注解形式来帮助我们简化代码,减少一些必须有但显得很冗余的 Java 代码。最常见的就是比如 getter/setter、日志变量等。通过 Lombok,我们可以以非常干净简洁的风格来写 Java 代码,不再需要编写那些无聊的 boilerplate 代码。

为什么要用它?

Lombok 的主要优点是可以减少 Java 代码的冗余,使代码更简洁。这有以下好处:

  1. 提高开发效率 - 不需要编写各种 getter/setter、日志语句等重复代码,节约大量时间。
  2. 代码更干净 - 没有大段冗余代码,逻辑更清晰。
  3. 更高效优雅 - 通过注解式编程,代码风格更优雅。
  4. 减少出错 - 自动生成的代码正确性更高。
  5. 开发人员更愉快 - 不需要编写无聊重复代码,可以更专注于业务
@Getter
@Setter
class User {
    private Integer id;
    private String username;
    private String avatar;
}
class User {

    private Integer id;
    private String username;
    private String avatar;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }
    // 其他getter/setter方法
}

Lombok 之所以强大,是因为它扩展了 Java 的编译过程。当我们在代码中添加 Lombok 注解时,例如@Getter/@Setter,Lombok 会在编译期间自动扫描这些注解,并为我们生成相应的代码,比如 getter/setter 方法。然后这些自动生成的方法就会被编译进最终的.class 文件中。所以使用 Lombok 注解后得到的 User.class 类文件中,已经包含了完整的 getter/setter 方法,这就是 Lombok 的神奇之处。正因如此,我们在编写代码时只需要添加注解,而不需要手动编写那些冗余代码,这极大地提高了我们的工作效率。

如何使用

使用 Lombok 很简单,只需要几个步骤:

  1. 在 Maven/Gradle 中添加 Lombok 依赖
  2. 在 IDE 中安装 Lombok 插件
  3. 在代码中使用 Lombok 注解

1. 添加 Lombok 依赖

在 Maven 项目中, 可以在 pom 中添加以下依赖来使用 lombok:

<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.30</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

在 Gradle 项目中,可以使用 lombok 插件:

plugins {
  id "io.freefair.lombok" version "8.4"
}

除了用 lombok gradle 插件以外,还可以使用 compilyOnly 添加依赖,实现只在编译时引入 lombok 的效果。build.gradle 可以这样配置:

repositories {
    mavenCentral()
}

dependencies {
    compileOnly 'org.projectlombok:lombok:1.18.30'
    annotationProcessor 'org.projectlombok:lombok:1.18.30'

    testCompileOnly 'org.projectlombok:lombok:1.18.30'
    testAnnotationProcessor 'org.projectlombok:lombok:1.18.30'
}

在 IDE 中安装 lombok 插件

除了在 maven/gradle 中安装依赖以外,我们还要在 IDE 中安装插件,否则会出现 IDE 报语法错误却能通过编译的现象, 而且 IDE 也无法正确提示自动生成的代码。所以 lombok 是一个侵入性很强的库,只要团队中有一个人用 lombok 开发, 那么所有人都必须安装 插件。不过好在 lombok 官方提供了丰富的插件支持,目前市面绝大多数流行的 IDE 和构建工具都有提供支持。具体可以参考官方文档,这里只摘抄了 IDEA 和 VsCode 作为例子。

1. 在 IDEA 中安装 lombok 插件

IntelliJ IDEA 2020.3 到 2023.1 之间的版本开始原生兼容 Lombok,无需安装插件即可使用。但在 2020.3 之前或 2023.1 之后的版本中,仍需借助 Lombok 插件来支持 Lombok 的所有功能。

对于这些版本的 IntelliJ IDEA,可以通过以下步骤安装 Lombok 插件:

  • 点击顶部菜单“File > Settings > Plugins”
  • 在弹出的窗口中,点击“Browse repositories...”
  • 在搜索框内输入“Lombok Plugin”进行搜索
  • 在搜索结果中找到“Lombok Plugin”,点击“Install”进行安装
  • 安装完成后重启 IntelliJ IDEA

2. 在 VsCode 中安装 lombok 插件

Extension Pack for Java 插件内置支持 lombok。

  • ctrl+shift+X 打开扩展管理器
  • 输入 java 查找插件,并点击 “安装”
  • 最后重新载入 VsCode

Lombok 用法

val

用在局部变量前面,将变量声明为 final

import java.util.ArrayList;
import java.util.HashMap;
import lombok.val;

public class ValExample {
  public String example() {
    val example = new ArrayList<String>();
    example.add("Hello, World!");
    val foo = example.get(0);
    return foo.toLowerCase();
  }
  public void example2() {
    val map = new HashMap<Integer, String>();
    map.put(0, "zero");
    map.put(5, "five");
    for (val entry : map.entrySet()) {
      System.out.printf("%d: %s\n", entry.getKey(), entry.getValue());
    }
  }
}
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

public class ValExample {
  public String example() {
    final ArrayList<String> example = new ArrayList<String>();
    example.add("Hello, World!");
    final String foo = example.get(0);
    return foo.toLowerCase();
  }

  public void example2() {
    final HashMap<Integer, String> map = new HashMap<Integer, String>();
    map.put(0, "zero");
    map.put(5, "five");
    for (final Map.Entry<Integer, String> entry : map.entrySet()) {
      System.out.printf("%d: %s\n", entry.getKey(), entry.getValue());
    }
  }
}

@Getter/@Setter

在属性上使用,让 lombok 自动生成对应的 getter/setter ,它还可以指定访问范围(包括:PUBLIC, PROTECTED, PACKAGE 和 PRIVATE)。如果将访问级别设置为特殊的 AccessLevel.NONE,就可以用来禁用 getter/setter 生成, 在类上使用 @Data 注释时,可以用此方法来禁用某些字段生成 getter/setter 方法。

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

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

  @Setter(AccessLevel.PROTECTED) private String name;
}
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;
  }
}

@ToString

在类上添加 @ToString 注解,可以自动覆写 toString 方法,还可以排除指定的属性以及设置在输出中包含父类的 toString 方法输出。

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

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

@EqualsAndHashCode

用在类上,自动生成 equals 和 hashCode 方法, 默认情况下会使用所有非静态、非顺时字段,也可以通过 @EqualsAndHashCode.Include 和 @EqualsAndHashCode.Exclude 来标记要包含和排除哪些字段。如果希望在一个子类的方法中包含父类的 equals/hashCode 调用,则需要将 callSuper 设置为 true。


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

@NoArgsConstructor, @RequiredArgsConstructor 和 @AllArgsConstructor

这些注解都是用在类上,用于自动生成构造函数。

  1. @NoArgsConstructor 用于生成无参构造函数
  2. @RequiredArgsConstructor 注解可以用来自动生成指定参数的构造方法。它所生成的构造方法,参数来源有两个:
    1. 类中所有未初始化的final字段
    2. 被@NonNull注解标记过的字段
      对于这两类字段,@RequiredArgsConstructor会自动为它们生成构造方法参数。并且为@NonNull标记的字段生成显式空检查,在接收到 null 参数值时抛出 NPE 异常。
  3. @AlllArgsConstructor 注解可以用来自动生成全参构造方法,标记为@NonNull的字段的参数还会自动加上空值校验。

另外,这些注解生成的构造方法参数顺序与类中字段声明的顺序一致。如果指定staticName="of"参数,则这些注解会生成一个返回类对象的静态工厂方法,比直接使用 new 语句方便很多。

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

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

@Data

在类上添加 @Data 注解,相当于同时添加了 @ToString, @EqualsAndHashCode@Getter@Setter@RequiredArgsConstrutor 这些注解,所有字段的 getter 方法 , 非 final 字段的 setter 方法,以及 toString, equals 和 hashCode 方法都会自动生成,对于快速实现 POJO 十分有用。

import lombok.AccessLevel;
import lombok.Setter;
import lombok.Data;
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;
  }
}
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;
    }
  }
}

@Log

lombok 为不同的日志框架内置支持了几种不同的注解,这些注解都是在类上生成一个名为 log 的静态常量,只是常量的实现不太一样:

  1. @CommonsLog: private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(LogExample.class);
  2. @Flogger : private static final com.google.common.flogger.FluentLogger log = com.google.common.flogger.FluentLogger.forEnclosingClass();
  3. @JBossLog: private static final org.jboss.logging.Logger log = org.jboss.logging.Logger.getLogger(LogExample.class);
  4. @Log: private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LogExample.class.getName());
  5. @Log4j: private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(LogExample.class);
  6. @Log4j2: private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class);
  7. @Slf4j: private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExample.class);
  8. @XSlf4j: private static final org.slf4j.ext.XLogger log = org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class);
import lombok.extern.java.Log;
import lombok.extern.slf4j.Slf4j;

@Log
public class LogExample {
  
  public static void main(String... args) {
    log.severe("Something's wrong here");
  }
}

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

@CommonsLog(topic="CounterLog")
public class LogExampleCategory {

  public static void main(String... args) {
    log.error("Calling the 'CounterLog' with a message");
  }
}
public class LogExample {
  private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LogExample.class.getName());
  
  public static void main(String... args) {
    log.severe("Something's wrong here");
  }
}

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

public class LogExampleCategory {
  private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog("CounterLog");

  public static void main(String... args) {
    log.error("Calling the 'CounterLog' with a message");
  }
}

除了内置的几个日志框架,lombok 还提供了自定义日志框架功能,只需要在类上添加@CustomLog注解,并且在 lombok.config 配置文件中指定 lombok.log.custom.declaration 配置项即可:

# lombok.config
lombok.log.custom.declaration = com.foo.your.Logger com.foo.your.LoggerFactory.createYourLog(TYPE)(TOPIC)

最后生成的 log 静态常量就像这样:

private static final com.foo.your.Logger log = com.foo.your.LoggerFactory.createYourLogger(LogExample.class);
posted on   y1j2x34  阅读(168)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗
< 2025年3月 >
23 24 25 26 27 28 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 1 2 3 4 5

点击右上角即可分享
微信分享提示