lombok使用基础教程

前言

lombok是一个编译级别的插件,它可以在项目编译的时候生成一些代码。在很多工具类的项目中都有这个功能。比如dagger。

通俗的说,lombok可以通过注解来标示生成getter settter等代码。我们自然可以通过编译器比如IDEA的Generate生成,为啥要用这个?

在项目开发阶段,一个class的属性是一直变化的,今天可能增加一个字段,明天可能删除一个字段。每次变化都需要修改对应的模板代码。另外,有的class的字段超级多,多到一眼看不完。如果加上模板代码,更难一眼看出来。更有甚者,由于字段太多,想要使用builder来创建。手动创建builder和字段和原来的类夹杂在一起,看起来真的难受。lombok的@Builder即可解决这个问题。

引入

引入就是加入lombok的jar包。

依赖

直接加入依赖

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

IDE 中 lombok 插件支持

eclipse 中

下载lombok

下载地址:https://projectlombok.org/downloads/lombok.jar

或者访问官网下载 https://projectlombok.org/

lombok使用基础教程

lombok使用基础教程

Intell IDEA 中

在IDEA里使用需要添加一个插件。在插件里搜索lombok,安装,重启。

IDEA里需要在设置中启用annotation processors。

lombok使用基础教程

基本用法

Getter Setter

最简单的,最常用的,最直观的使用就是getter setter方法。

import java.util.Date;

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

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

    @Getter
    @Setter
    private Boolean none;

    @Getter
    @Setter
    private Date date;
    
    @Setter(AccessLevel.PROTECTED)
    private String name;
    
    @Override
    public String toString() {
        return String.format("%s (age: %d)", name, age);
    }
    
    @SuppressWarnings("unused")
    public static void main(String[] args) {
        GetterSetterExample example = new GetterSetterExample();
        example.setActive(true);
        example.setAge(123);
        example.setDate(new Date());
        example.setName("abc");
        example.setNone(false);

        System.out.println(example);
        
        Date date = example.getDate();
        Boolean none = example.getNone();
        boolean active = example.isActive();
    }
    
}

简单使用没有问题,深入一点可以看到有些特殊设定。比如javadoc.

  • Getter声明创建getter方法;
  • Setter声明创建setter方法;
  • @Setter(AccessLevel.PROTECTED)可以添加参数,指定权限为继承;
  • Attention!关于boolean的set前缀都是set,但getter不同,小写的boolean,即基本类型,前缀是is; Boolean,即包装类型,前缀是get;

编译后的结果如下:

import java.io.PrintStream;
import java.util.Date;

public class GetterSetterExample
{
  public int getAge()
  {
    return this.age;
  }
  
  public void setAge(int age)
  {
    this.age = age;
  }
  
  private int age = 10;
  private boolean active;
  private Boolean none;
  private Date date;
  private String name;
  
  public boolean isActive()
  {
    return this.active;
  }
  
  public void setActive(boolean active)
  {
    this.active = active;
  }
  
  public Boolean getNone()
  {
    return this.none;
  }
  
  public void setNone(Boolean none)
  {
    this.none = none;
  }
  
  public Date getDate()
  {
    return this.date;
  }
  
  public void setDate(Date date)
  {
    this.date = date;
  }
  
  protected void setName(String name)
  {
    this.name = name;
  }
  
  public String toString()
  {
    return String.format("%s (age: %d)", new Object[] { this.name, Integer.valueOf(this.age) });
  }
  
  public static void main(String[] args)
  {
    GetterSetterExample example = new GetterSetterExample();
    example.setActive(true);
    example.setAge(123);
    example.setDate(new Date());
    example.setName("abc");
    example.setNone(Boolean.valueOf(false));
    
    System.out.println(example);
    
    Date date = example.getDate();
    Boolean none = example.getNone();
    boolean active = example.isActive();
  }
}

ToString

虽然ToString在生产环境貌似没什么卵用。但是,很多情况下,我们还是需要这个的。因为记log。不想每次看log的时候是一串@地址,那就好好把toString()加上。

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Setter
@ToString(exclude = "id")
public class ToStringExample {
    
    @Getter
    private static final int STATIC_VAR = 10;
    private String name;
    private Shape shape = new Square(5, 10);
    private String[] tags;
    @Getter
    private int id;

    @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;
        }
    }
    
    @ToString
    public static class Shape {
        private int color;
    }
    
    public static void main(String[] args) {
        final ToStringExample example = new ToStringExample();
        example.setId(1);
        example.setName("abc");
        example.setTags(new String[]{"a", "b", "c"});
        
        final Shape shape = new Square(1, 2);
        example.setShape(shape);
        
        System.out.println(example.toString());
    }
}

ToString 一些需要注意的地方:

  • exclude 属性可以指定不需要在 toString() 方法中输出的属性
  • callSupper 属性可以在会调用父类的 toString 方法,在返回中会出现 super=…
  • includeFieldNames 属性在 toString 中打印时显示字段的名称,默认为 true

打印结果如下:

ToStringExample(name=abc, shape=ToStringExample.Square(super=ToStringExample.Shape(color=0), width=1, height=2), tags=[a, b, c])

编译后的代码如下:

import java.io.PrintStream;
import java.util.Arrays;

public class ToStringExample
{
  private static final int STATIC_VAR = 10;
  private String name;
  
  public void setTags(String[] tags)
  {
    this.tags = tags;
  }
  
  public void setId(int id)
  {
    this.id = id;
  }
  
  public void setName(String name)
  {
    this.name = name;
  }
  
  public void setShape(Shape shape)
  {
    this.shape = shape;
  }
  
  public String toString()
  {
    return "ToStringExample(name=" + this.name + ", shape=" + this.shape + ", tags=" + Arrays.deepToString(this.tags) + ")";
  }
  
  public static int getSTATIC_VAR()
  {
    return 10;
  }
  
  private Shape shape = new Square(5, 10);
  private String[] tags;
  private int id;
  
  public int getId()
  {
    return this.id;
  }
  
  public static class Square
    extends ToStringExample.Shape
  {
    private final int width;
    private final int height;
    
    public String toString()
    {
      return "ToStringExample.Square(super=" + super.toString() + ", width=" + this.width + ", height=" + this.height + ")";
    }
    
    public Square(int width, int height)
    {
      this.width = width;
      this.height = height;
    }
  }
  
  public static class Shape
  {
    private int color;
    
    public String toString()
    {
      return "ToStringExample.Shape(color=" + this.color + ")";
    }
  }
  
  public static void main(String[] args)
  {
    ToStringExample example = new ToStringExample();
    example.setId(1);
    example.setName("abc");
    example.setTags(new String[] { "a", "b", "c" });
    
    Shape shape = new Square(1, 2);
    example.setShape(shape);
    
    System.out.println(example.toString());
  }
}

@EqualsAndHashCode

equals()和hashCode()在Java中有着举足轻重的基地作用,虽然通常很少关注。但是,这个必须不可省。不知道有几个可以手写出来的。

import com.sun.org.apache.xalan.internal.xsltc.compiler.sym;

import lombok.EqualsAndHashCode;

@EqualsAndHashCode(exclude = { "id", "shape" })
@SuppressWarnings("unused")
public class EqualsAndHashCodeExample {

    private transient int transientVar = 10;
    private String name;
    private double score;
    private String[] tags;
    private int id;
    private ToStringExample.Shape shape = new Square(5, 10);

    @EqualsAndHashCode(callSuper=true)
    public static class Square extends ToStringExample.Shape {
        private final int width, height;

        public Square(int width, int height) {
            this.width = width;
            this.height = height;
        }
    }

    public static void main(String[] args) {
        EqualsAndHashCodeExample example = new EqualsAndHashCodeExample();
        EqualsAndHashCodeExample example1 = new EqualsAndHashCodeExample();
        boolean equals = example.equals(example1);
        System.out.println(equals);
        
        boolean b = example.canEqual(example);
        System.out.println(b);
        
        int i = example.hashCode();
        System.out.println(i);
    }
    
}

注意:

  • exclude 属性可以排除一些不需要用于判断两个对象是否 equals 的属性
  • callSuper 属性可以用于父类的 equals 方法判断,如果父类的 equals 方法返回false,则子类的equals方法直接返回 false。详见下面编译后的结果。

编译后的结果为:

import java.io.PrintStream;
import java.util.Arrays;

public class EqualsAndHashCodeExample
{
  public int hashCode()
  {
    int PRIME = 59;int result = 1;Object $name = this.name;result = result * 59 + ($name == null ? 43 : $name.hashCode());long $score = Double.doubleToLongBits(this.score);result = result * 59 + (int)($score ^ $score >>> 32);result = result * 59 + Arrays.deepHashCode(this.tags);return result;
  }
  
  protected boolean canEqual(Object other)
  {
    return other instanceof EqualsAndHashCodeExample;
  }
  
  public boolean equals(Object o)
  {
    if (o == this) {
      return true;
    }
    if (!(o instanceof EqualsAndHashCodeExample)) {
      return false;
    }
    EqualsAndHashCodeExample other = (EqualsAndHashCodeExample)o;
    if (!other.canEqual(this)) {
      return false;
    }
    Object this$name = this.name;Object other$name = other.name;
    if (this$name == null ? other$name != null : !this$name.equals(other$name)) {
      return false;
    }
    if (Double.compare(this.score, other.score) != 0) {
      return false;
    }
    return Arrays.deepEquals(this.tags, other.tags);
  }
  
  private transient int transientVar = 10;
  private String name;
  private double score;
  private String[] tags;
  private int id;
  private ToStringExample.Shape shape = new Square(5, 10);
  
  public static class Square
    extends ToStringExample.Shape
  {
    private final int width;
    private final int height;
    
    public int hashCode()
    {
      int PRIME = 59;int result = super.hashCode();result = result * 59 + this.width;result = result * 59 + this.height;return result;
    }
    
    protected boolean canEqual(Object other)
    {
      return other instanceof Square;
    }
    
    public boolean equals(Object o)
    {
      if (o == this) {
        return true;
      }
      if (!(o instanceof Square)) {
        return false;
      }
      Square other = (Square)o;
      if (!other.canEqual(this)) {
        return false;
      }
      if (!super.equals(o)) {
        return false;
      }
      if (this.width != other.width) {
        return false;
      }
      return this.height == other.height;
    }
    
    public Square(int width, int height)
    {
      this.width = width;
      this.height = height;
    }
  }
  
  public static void main(String[] args)
  {
    EqualsAndHashCodeExample example = new EqualsAndHashCodeExample();
    EqualsAndHashCodeExample example1 = new EqualsAndHashCodeExample();
    boolean equals = example.equals(example1);
    System.out.println(equals);
    
    boolean b = example.canEqual(example);
    System.out.println(b);
    
    int i = example.hashCode();
    System.out.println(i);
  }
}

构造函数@NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor

Java中class的一切起源于构造器。大家最喜欢的还是构造函数创建对象。这里有一点比较坑的是无参构造函数。当你自己添加一个带有参数的构造函数后,无参构造函数则被隐藏。通常也没啥问题,但当你使用jackson反序列化对象的时候就被恶心到了。jackson通过无参构造函数创建对象。因此,当你考虑这个class会用来序列化为json的时候,即必须手动添加一个无参数构造函数。

@NoArgsConstructor

当你想要创建一个valueobject,DDD中的值对象,要求实现Immutable,那么无参数构造器就不合适了。@NoArgsConstructor会生成一个空的构造器。如果你设置了final field,那么编译会报错。如果你强制执行创建无参数构造器。即,@NoArgsConstructor(force = true),那么final的field会初始化为0/false/null。通常适合与@Data集成。

import lombok.NoArgsConstructor;
import lombok.NonNull;

@NoArgsConstructor(force=true)
public class NoArgsExample {

    @NonNull
    private final String field;
    
    public static void main(String[] args) {
        new NoArgsExample();
    }
    
}
  • @NonNull 被忽略了

最终生成的代码如下:

import lombok.NonNull;

public class NoArgsExample
{
  @NonNull
  private final String field = null;
  
  public static void main(String[] args)
  {
    new NoArgsExample();
  }
}

对于final的字段,我认为我不会用空构造器来做这件事。所以,感觉这个参数force=true不要也罢,鸡肋。

@RequiredArgsConstructor

一个class可以有很多属性,但你可能只关心其中的几个字段,那么可以使用@RequiredArgsConstructor。@NonNull将标注这个字段不应为null,初始化的时候会检查是否为空,否则抛出NullPointException。在上面的无参构造函数中被忽略了。那么,对于关注的字段标注@NonNull, @RequiredArgsConstructor则会生成带有这些字段的构造器。

import java.util.Date;

import lombok.NonNull;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@SuppressWarnings("unused")
public class RequiredArgsExample {

    @NonNull
    private String field;
    
    private Date date;
    private Integer integer;
    private int i;
    private boolean b;
    private Boolean aBoolean;
    
}

最终生成结果:

import java.util.Date;
import lombok.NonNull;

public class RequiredArgsExample
{
  @NonNull
  private String field;
  private Date date;
  private Integer integer;
  private int i;
  private boolean b;
  private Boolean aBoolean;
  
  public RequiredArgsExample(@NonNull String field)
  {
    if (field == null) {
      throw new NullPointerException("field is marked non-null but is null");
    }
    this.field = field;
  }
}

只有@NonNull会生成构造器。其他默认,Java的class初始化默认为null.false,0.

lombok提供了另一种初始化做法,静态初始化。即私有构造器,使用静态方法创建对象。这种做法看起来简单,但通常用的不多。因为静态初始化的东西很难mock,对测试不够友好。

import java.util.Date;

import lombok.NonNull;
import lombok.RequiredArgsConstructor;

@SuppressWarnings("unused")
@RequiredArgsConstructor(staticName="of")
public class RequiredArgsStaticExample {

    @NonNull
    private String field;
    
    private Date date;
    private Integer integer;
    private int i;
    private boolean b;
    private Boolean aBoolean;
    
}

最终生成代码如下:

import java.util.Date;
import lombok.NonNull;

public class RequiredArgsStaticExample
{
  @NonNull
  private String field;
  private Date date;
  private Integer integer;
  private int i;
  private boolean b;
  private Boolean aBoolean;
  
  public static RequiredArgsStaticExample of(@NonNull String field)
  {
    return new RequiredArgsStaticExample(field);
  }
  
  private RequiredArgsStaticExample(@NonNull String field)
  {
    if (field == null) {
      throw new NullPointerException("field is marked non-null but is null");
    }
    this.field = field;
  }
}

@AllArgsConstructor

想要初始化所有字段。

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

@AllArgsConstructor(access=AccessLevel.PROTECTED)
public class AllArgsConstructorExample<T> {

    @SuppressWarnings("unused")
    private int x, y;
    
    @NonNull private T description;
    
}

最终生成代码如下:

import lombok.NonNull;

public class AllArgsConstructorExample<T>
{
  private int x;
  private int y;
  @NonNull
  private T description;
  
  protected AllArgsConstructorExample(int x, int y, @NonNull T description)
  {
    if (description == null) {
      throw new NullPointerException("description is marked non-null but is null");
    }
    this.x = x;this.y = y;this.description = description;
  }
}

必用项 @Data

@Data是一个集合体。包含Getter,Setter,RequiredArgsConstructor,ToString,EqualsAndHashCode

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

最终生成代码如下:

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 toString()
  {
    return "DataExample(name=" + getName() + ", age=" + getAge() + ", score=" + getScore() + ", tags=" + Arrays.deepToString(getTags()) + ")";
  }
  
  public int hashCode()
  {
    int PRIME = 59;int result = 1;Object $name = getName();result = result * 59 + ($name == null ? 43 : $name.hashCode());result = result * 59 + getAge();long $score = Double.doubleToLongBits(getScore());result = result * 59 + (int)($score ^ $score >>> 32);result = result * 59 + Arrays.deepHashCode(getTags());return result;
  }
  
  protected boolean canEqual(Object other)
  {
    return other instanceof DataExample;
  }
  
  public boolean equals(Object o)
  {
    if (o == this) {
      return true;
    }
    if (!(o instanceof DataExample)) {
      return false;
    }
    DataExample other = (DataExample)o;
    if (!other.canEqual(this)) {
      return false;
    }
    Object this$name = getName();Object other$name = other.getName();
    if (this$name == null ? other$name != null : !this$name.equals(other$name)) {
      return false;
    }
    if (getAge() != other.getAge()) {
      return false;
    }
    if (Double.compare(getScore(), other.getScore()) != 0) {
      return false;
    }
    return Arrays.deepEquals(getTags(), other.getTags());
  }
  
  public void setTags(String[] tags)
  {
    this.tags = tags;
  }
  
  public void setScore(double score)
  {
    this.score = score;
  }
  
  public String[] getTags()
  {
    return this.tags;
  }
  
  public double getScore()
  {
    return this.score;
  }
  
  public int getAge()
  {
    return this.age;
  }
  
  public String getName()
  {
    return this.name;
  }
  
  void setAge(int age)
  {
    this.age = age;
  }
  
  public static class Exercise<T>
  {
    private final String name;
    private final T valueT;
    
    public String toString()
    {
      return "DataExample.Exercise(name=" + getName() + ", valueT=" + getValueT() + ")";
    }
    
    public String getName()
    {
      return this.name;
    }
    
    public T getValueT()
    {
      return this.valueT;
    }
    
    public boolean equals(Object o)
    {
      if (o == this) {
        return true;
      }
      if (!(o instanceof Exercise)) {
        return false;
      }
      Exercise<?> other = (Exercise)o;
      if (!other.canEqual(this)) {
        return false;
      }
      Object this$name = getName();Object other$name = other.getName();
      if (this$name == null ? other$name != null : !this$name.equals(other$name)) {
        return false;
      }
      Object this$valueT = getValueT();Object other$valueT = other.getValueT();return this$valueT == null ? other$valueT == null : this$valueT.equals(other$valueT);
    }
    
    protected boolean canEqual(Object other)
    {
      return other instanceof Exercise;
    }
    
    public int hashCode()
    {
      int PRIME = 59;int result = 1;Object $name = getName();result = result * 59 + ($name == null ? 43 : $name.hashCode());Object $valueT = getValueT();result = result * 59 + ($valueT == null ? 43 : $valueT.hashCode());return result;
    }
    
    private Exercise(String name, T valueT)
    {
      this.name = name;this.valueT = valueT;
    }
    
    public static <T> Exercise<T> of(String name, T valueT)
    {
      return new Exercise(name, valueT);
    }
  }
}

不可变对象value object @Value

这个看起来很美好,就是可以帮忙生成一个不可变对象。对于所有的字段都将生成final的。但我感觉有点失控。注解的优势应该是所见即所得,可以通过字面量来传递消息。而@Value字段给字段加final会让人困惑,因为这更改了我们的定义。当我想声明一个Immutable对象的时候,我会显示的给字段加一个限定final。

同@Data, @Value是一个集合体。包含Getter,AllArgsConstructor,ToString,EqualsAndHashCode。

import java.util.Date;

import lombok.NonNull;
import lombok.Value;

@Value
public class ValueExample {
    
    @NonNull
    private String id;
    private String name;
    private boolean active;
    private Date createTime;
    
}

编译后

import java.util.Date;
import lombok.NonNull;

public final class ValueExample
{
  @NonNull
  private final String id;
  private final String name;
  private final boolean active;
  private final Date createTime;
  
  public ValueExample(@NonNull String id, String name, boolean active, Date createTime)
  {
    if (id == null) {
      throw new NullPointerException("id is marked non-null but is null");
    }
    this.id = id;this.name = name;this.active = active;this.createTime = createTime;
  }
  
  public String toString()
  {
    return "ValueExample(id=" + getId() + ", name=" + getName() + ", active=" + isActive() + ", createTime=" + getCreateTime() + ")";
  }
  
  public int hashCode()
  {
    int PRIME = 59;int result = 1;Object $id = getId();result = result * 59 + ($id == null ? 43 : $id.hashCode());Object $name = getName();result = result * 59 + ($name == null ? 43 : $name.hashCode());result = result * 59 + (isActive() ? 79 : 97);Object $createTime = getCreateTime();result = result * 59 + ($createTime == null ? 43 : $createTime.hashCode());return result;
  }
  
  public boolean equals(Object o)
  {
    if (o == this) {
      return true;
    }
    if (!(o instanceof ValueExample)) {
      return false;
    }
    ValueExample other = (ValueExample)o;Object this$id = getId();Object other$id = other.getId();
    if (this$id == null ? other$id != null : !this$id.equals(other$id)) {
      return false;
    }
    Object this$name = getName();Object other$name = other.getName();
    if (this$name == null ? other$name != null : !this$name.equals(other$name)) {
      return false;
    }
    if (isActive() != other.isActive()) {
      return false;
    }
    Object this$createTime = getCreateTime();Object other$createTime = other.getCreateTime();return this$createTime == null ? other$createTime == null : this$createTime.equals(other$createTime);
  }
  
  public Date getCreateTime()
  {
    return this.createTime;
  }
  
  public boolean isActive()
  {
    return this.active;
  }
  
  public String getName()
  {
    return this.name;
  }
  
  @NonNull
  public String getId()
  {
    return this.id;
  }
}

最喜欢的项 @Builder

对于喜欢builder模式的人来说,声明式简化对象创建流程让一切看得美好。但是,手动复制字段,手动创建方法很让人不喜。@Builder解决了刚需。

import java.util.Date;
import java.util.Set;

import org.junit.Assert;

import lombok.Builder;
import lombok.Data;
import lombok.NonNull;
import lombok.Singular;

@Data
@Builder(toBuilder = true)
public class BuilderExample {

    @NonNull
    private String id;
    private String name;
    private boolean active;
    private Date createTime;

    @Singular
    private Set<String> occupations;

    public static void main(String[] args) {
        BuilderExample builer = BuilderExample.builder().active(true)
                        .name("name")
                        .id("id")
                        .createTime(new Date())
                        .occupation("1")
                        .occupation("2")
                        .build();
        Assert.assertEquals(2, builer.getOccupations().size());
    }
    
}

这才是我们想要的建造者。对应生成的代码为:

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.Set;
import lombok.NonNull;
import org.junit.Assert;

public class BuilderExample
{
  @NonNull
  private String id;
  private String name;
  private boolean active;
  private Date createTime;
  private Set<String> occupations;
  
  public String toString()
  {
    return "BuilderExample(id=" + getId() + ", name=" + getName() + ", active=" + isActive() + ", createTime=" + getCreateTime() + ", occupations=" + getOccupations() + ")";
  }
  
  public int hashCode()
  {
    int PRIME = 59;int result = 1;Object $id = getId();result = result * 59 + ($id == null ? 43 : $id.hashCode());Object $name = getName();result = result * 59 + ($name == null ? 43 : $name.hashCode());result = result * 59 + (isActive() ? 79 : 97);Object $createTime = getCreateTime();result = result * 59 + ($createTime == null ? 43 : $createTime.hashCode());Object $occupations = getOccupations();result = result * 59 + ($occupations == null ? 43 : $occupations.hashCode());return result;
  }
  
  protected boolean canEqual(Object other)
  {
    return other instanceof BuilderExample;
  }
  
  public boolean equals(Object o)
  {
    if (o == this) {
      return true;
    }
    if (!(o instanceof BuilderExample)) {
      return false;
    }
    BuilderExample other = (BuilderExample)o;
    if (!other.canEqual(this)) {
      return false;
    }
    Object this$id = getId();Object other$id = other.getId();
    if (this$id == null ? other$id != null : !this$id.equals(other$id)) {
      return false;
    }
    Object this$name = getName();Object other$name = other.getName();
    if (this$name == null ? other$name != null : !this$name.equals(other$name)) {
      return false;
    }
    if (isActive() != other.isActive()) {
      return false;
    }
    Object this$createTime = getCreateTime();Object other$createTime = other.getCreateTime();
    if (this$createTime == null ? other$createTime != null : !this$createTime.equals(other$createTime)) {
      return false;
    }
    Object this$occupations = getOccupations();Object other$occupations = other.getOccupations();return this$occupations == null ? other$occupations == null : this$occupations.equals(other$occupations);
  }
  
  public void setOccupations(Set<String> occupations)
  {
    this.occupations = occupations;
  }
  
  public void setCreateTime(Date createTime)
  {
    this.createTime = createTime;
  }
  
  public void setActive(boolean active)
  {
    this.active = active;
  }
  
  public void setName(String name)
  {
    this.name = name;
  }
  
  public void setId(@NonNull String id)
  {
    if (id == null) {
      throw new NullPointerException("id is marked non-null but is null");
    }
    this.id = id;
  }
  
  public Set<String> getOccupations()
  {
    return this.occupations;
  }
  
  public Date getCreateTime()
  {
    return this.createTime;
  }
  
  public boolean isActive()
  {
    return this.active;
  }
  
  public String getName()
  {
    return this.name;
  }
  
  @NonNull
  public String getId()
  {
    return this.id;
  }
  
  public static class BuilderExampleBuilder
  {
    private String id;
    private String name;
    private boolean active;
    private Date createTime;
    private ArrayList<String> occupations;
    
    public String toString()
    {
      return "BuilderExample.BuilderExampleBuilder(id=" + this.id + ", name=" + this.name + ", active=" + this.active + ", createTime=" + this.createTime + ", occupations=" + this.occupations + ")";
    }
    
    public BuilderExample build()
    {
      Set<String> occupations;
      Set<String> occupations;
      Set<String> occupations;
      switch (this.occupations == null ? 0 : this.occupations.size())
      {
      case 0: 
        occupations = Collections.emptySet(); break;
      case 1: 
        occupations = Collections.singleton((String)this.occupations.get(0)); break;
      default: 
        occupations = new LinkedHashSet(this.occupations.size() < 1073741824 ? 1 + this.occupations.size() + (this.occupations.size() - 3) / 3 : 2147483647);occupations.addAll(this.occupations);occupations = Collections.unmodifiableSet(occupations);
      }
      return new BuilderExample(this.id, this.name, this.active, this.createTime, occupations);
    }
    
    public BuilderExampleBuilder clearOccupations()
    {
      if (this.occupations != null) {
        this.occupations.clear();
      }
      return this;
    }
    
    public BuilderExampleBuilder occupations(Collection<? extends String> occupations)
    {
      if (this.occupations == null) {
        this.occupations = new ArrayList();
      }
      this.occupations.addAll(occupations);return this;
    }
    
    public BuilderExampleBuilder occupation(String occupation)
    {
      if (this.occupations == null) {
        this.occupations = new ArrayList();
      }
      this.occupations.add(occupation);return this;
    }
    
    public BuilderExampleBuilder createTime(Date createTime)
    {
      this.createTime = createTime;return this;
    }
    
    public BuilderExampleBuilder active(boolean active)
    {
      this.active = active;return this;
    }
    
    public BuilderExampleBuilder name(String name)
    {
      this.name = name;return this;
    }
    
    public BuilderExampleBuilder id(@NonNull String id)
    {
      if (id == null) {
        throw new NullPointerException("id is marked non-null but is null");
      }
      this.id = id;return this;
    }
  }
  
  public BuilderExampleBuilder toBuilder()
  {
    BuilderExampleBuilder builder = new BuilderExampleBuilder().id(this.id).name(this.name).active(this.active).createTime(this.createTime);
    if (this.occupations != null) {
      builder.occupations(this.occupations);
    }
    return builder;
  }
  
  public static BuilderExampleBuilder builder()
  {
    return new BuilderExampleBuilder();
  }
  
  BuilderExample(@NonNull String id, String name, boolean active, Date createTime, Set<String> occupations)
  {
    if (id == null) {
      throw new NullPointerException("id is marked non-null but is null");
    }
    this.id = id;this.name = name;this.active = active;this.createTime = createTime;this.occupations = occupations;
  }
  
  public static void main(String[] args)
  {
    BuilderExample builer = builder().active(true)
      .name("name")
      .id("id")
      .createTime(new Date())
      .occupation("1")
      .occupation("2")
      .build();
    Assert.assertEquals(2L, builer.getOccupations().size());
  }
}

@Slf4j

日志相关的注解。这里以 @Slf4j 为例。

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class LogExample {

    public static void main(String[] args) {
        log.error("Something else is wrong here");
    }
    
}

这里需要添加 slf4j 以及它的实现类的依赖

<!-- @Slf4j 支持 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.25</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>

编译后

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LogExample
{
  private static final Logger log = LoggerFactory.getLogger(LogExample.class);
  
  public static void main(String[] args)
  {
    log.error("Something else is wrong here");
  }
}

val

可以使用 val 声明本地变量的类型而不用写出它的实际类型,它实际的类型将由初始化的表达式推断。

import java.util.HashMap;

import lombok.val;

public class ValExample {

    public static void main(String[] args) {
        // lombok.val 使用
        val map = new HashMap<Integer, String>();
        map.put(0, "zero");
        map.put(5, "five");
        for(val entry : map.entrySet()) {
            System.out.println(entry.getKey() + ":" + entry.getValue());
        }
    }
    
}

编译后

import java.io.PrintStream;
import java.util.HashMap;
import java.util.Map.Entry;

public class ValExample
{
  public static void main(String[] args)
  {
    HashMap<Integer, String> map = new HashMap();
    map.put(Integer.valueOf(0), "zero");
    map.put(Integer.valueOf(5), "five");
    for (Map.Entry<Integer, String> entry : map.entrySet()) {
      System.out.println(entry.getKey() + ":" + (String)entry.getValue());
    }
  }
}

Lombok工作原理分析

总结

lombok还提供了其他几个注解,以及还有好多内置的参数没有讲解。但是,根据2-8原理,我们根本不需要。上面这几个足够了。更多的注解只会增加理解阅读难度。

会发现在Lombok使用的过程中,只需要添加相应的注解,无需再为此写任何代码。自动生成的代码到底是如何产生的呢?

核心之处就是对于注解的解析上。JDK5引入了注解的同时,也提供了两种解析方式。

  • 运行时解析
    运行时能够解析的注解,必须将@Retention设置为RUNTIME,这样就可以通过反射拿到该注解。java.lang,reflect反射包中提供了一个接口AnnotatedElement,该接口定义了获取注解信息的几个方法,Class、Constructor、Field、Method、Package等都实现了该接口,对反射熟悉的朋友应该都会很熟悉这种解析方式。

  • 编译时解析
    编译时解析有两种机制,分别简单描述下:

1)Annotation Processing Tool

apt自JDK5产生,JDK7已标记为过期,不推荐使用,JDK8中已彻底删除,自JDK6开始,可以使用Pluggable Annotation Processing API来替换它,apt被替换主要有2点原因:

  • api都在com.sun.mirror非标准包下
  • 没有集成到javac中,需要额外运行

2)Pluggable Annotation Processing API

JSR 269自JDK6加入,作为apt的替代方案,它解决了apt的两个问题,javac在执行的时候会调用实现了该API的程序,这样我们就可以对编译器做一些增强,这时javac执行的过程如下:

lombok使用基础教程

Lombok本质上就是一个实现了“JSR 269 API”的程序。在使用javac的过程中,它产生作用的具体流程如下:

  • javac对源代码进行分析,生成了一棵抽象语法树(AST)
  • 运行过程中调用实现了“JSR 269 API”的Lombok程序
  • 此时Lombok就对第一步骤得到的AST进行处理,找到@Data注解所在类对应的语法树(AST),然后修改该语法树(AST),增加getter和setter方法定义的相应树节点
  • javac使用修改后的抽象语法树(AST)生成字节码文件,即给class增加新的节点(代码块)

拜读了Lombok源码,对应注解的实现都在HandleXXX中,比如@Getter注解的实现时HandleGetter.handle()。还有一些其它类库使用这种方式实现,比如Google AutoDagger等等。

4. Lombok的优缺点

优点:

  • 能通过注解的形式自动生成构造器、getter/setter、equals、hashcode、toString等方法,提高了一定的开发效率
  • 让代码变得简洁,不用过多的去关注相应的方法
  • 属性做修改时,也简化了维护为这些属性所生成的getter/setter方法等

缺点:

  • 不支持多种参数构造器的重载
  • 虽然省去了手动创建getter/setter方法的麻烦,但大大降低了源代码的可读性和完整性,降低了阅读源代码的舒适度

本文转自

  1. lombok使用基础教程

  2. eclipse 安装lombok插件

  3. 使用@Slf4j的正确方法

  4. Lombok介绍、使用方法和总结