Lombok——減少Java樣板代碼的利器

Lombok簡介

Lombok是一种Java的实用工具,可用来帮助开发人员消除Java的冗长代码,尤其是对于简单的Java对象(POJO)。它通过注释实现这一目的。通过在开发环境中实现Lombok,开发人员可以节省构建getter/setter以及诸如hashCode()和equals()这样的方法。
Lombok会在编译时修改插入代码,因此不会影响任何运行时的性能。

安裝Lombok

使用Lombok非常简单,只需要在Maven中引入即可,该库仅在编译期生效,但足以把Java改造的像Kotlin一类的动态语言。

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

Jetbrains IntelliJ IDEA用户需要安装插件,已解决错误提示的问题,并提供自动补全、显示生成对象/函数的功能。

Lombok用法

@Data注解

@Data标注在class上,包含了以下几种注解:

注解 生成物
@Getter / @Setter getter / setter 方法
@ToString 包含类名和成员键值对的字符串
@EqualsAndHashCode equals()hashCode()方法
@RequiredArgsConstructor 包含非空参数的构造器
@AllArgsConstructor 包含所有非static final参数的构造器

@Data注解会为类的所有成员应用上述注解,示例代码:

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

// 等同于

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) {
    // 省略
  }

  @Override public int hashCode() {
    // 省略
  }

  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) {
      // 省略
    }

    @Override public int hashCode() {
      // 省略
    }
  }
}

@Value注解

@Value@Data的不可变形式,同样标注在class上,但是类本身、所有成员都变成了final的,并且不会生成setter。

@Builder注解

@Builder注解标注在类、构造器或方法上,可生成各种builder APIs,允许开发者以如下方式调用:

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

更多说明参考官方文档

@Log注解

注解在class上,并根据不同的注解生成不同类型的日志logger对象,但是实例名称都是log,修饰属性都是private static final,目前支持以下几种logger:

注解 生成对象
@CommonsLog org.apache.commons.logging.Log
@JBossLog org.jboss.logging.Logger
@Log java.util.logging.Logger
@Log4j org.apache.log4j.Logger
@Log4j2 org.apache.log4j.LogManager
@Slf4j org.slf4j.Logger
@XSlf4j org.slf4j.ext.XLogger

@Slf4j相当于:

private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(getClass());

@CommonsLog(topic="CounterLog")相当于:

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

@XXXConstructor注解

该注解提供自动生成构造函数的功能:

注解 生成构造器
@NoArgsConstructor 无参构造器
@RequiredArgsConstructor 包含非空参数的构造器
@AllArgsConstructor 包含所有非static final参数的构造器

示例代码如下:

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

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

类型推断

该功能类似于Kotlin等动态语言的变量类型推断:

  • val生成不可变的final对象,如val example = new ArrayList<String>();
    等同于代码final ArrayList<String> example = new ArrayList<>();
  • var生成可变的非final对象,如val x = "Hello";
    等同于代码String x = "Hello";

@NonNull注解

该注解提供非空检查功能:
如例:

public NonNullExample(@NonNull Person person) {
    super("Hello");
    this.name = person.getName();
}
// 等同于
public NonNullExample(Person person) {
    super("Hello");
    if (person == null) {
      throw new NullPointerException("person");
    }
    this.name = person.getName();
}

@SneakyThrows注解

@SneakyThrows注解的方法会自动抛出受检异常,而无需显式在方法上使用throws语句。

@Synchronized注解

@Synchronized注解标注在方法上,将方法声明为同步的,并自动加锁,而锁对象是一个私有的属性$lock或$LOCK。示例代码:

public class SynchronizedExample {
  private final Object readLock = new Object();

  @Synchronized
  public static void hello() {
    System.out.println("world");
  }

  @Synchronized
  public int answerToLife() {
    return 42;
  }

  @Synchronized("readLock")
  public void foo() {
    System.out.println("bar");
  }
}

// 等同于

public class SynchronizedExample {
  private static final Object $LOCK = new Object[0];
  private final Object $lock = new Object[0];
  private final Object readLock = new Object();

  public static void hello() {
    synchronized($LOCK) {
      System.out.println("world");
    }
  }

  public int answerToLife() {
    synchronized($lock) {
      return 42;
    }
  }

  public void foo() {
    synchronized(readLock) {
      System.out.println("bar");
    }
  }
}

@Getter(lazy=true)注解

@Getter(lazy=true)注解可以替代经典的Double Check Lock样板代码:

public class GetterLazyExample {
  @Getter(lazy=true) private final double[] cached = expensive();

  private double[] expensive() {
    double[] result = new double[1000000];
    for (int i = 0; i < result.length; i++) {
      result[i] = Math.asin(i);
    }
    return result;
  }
}

// 等同于

 public class GetterLazyExample {
  private final java.util.concurrent.AtomicReference<java.lang.Object> cached = new java.util.concurrent.AtomicReference<>();

  public double[] getCached() {
    java.lang.Object value = this.cached.get();
    if (value == null) {
      synchronized(this.cached) {
        value = this.cached.get();
        if (value == null) {
          final double[] actualValue = expensive();
          value = actualValue == null ? this.cached : actualValue;
          this.cached.set(value);
        }
      }
    }
    return (double[])(value == this.cached ? null : value);
  }

  private double[] expensive() {
    double[] result = new double[1000000];
    for (int i = 0; i < result.length; i++) {
      result[i] = Math.asin(i);
    }
    return result;
  }
}