1. 程式人生 > >極簡程式碼神器:Lombok使用教程

極簡程式碼神器:Lombok使用教程

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的訪問級別有 PUBLICPROTECTEDPACKAGE, 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 為其生成 equals和 hashCode 方法。預設情況下,將會用在非靜態,非 transient 標記的欄位上,但是你可以通過 @EqualsAndHashCode.Include或 @EqualsAndHashCode.Exclude 標記型別成員來修改使用哪些欄位。

或者,你可以通過使用 @EqualsAndHashCode.Include 並使用 @EqualsAndHashCode(onlyExplicitlyIncluded = true)標記它們來準確指定你希望使用的欄位或方法。

如果將 @EqualsAndHashCode 應用於擴充套件另一個的類,這個特性就會變的很危險。通常來說,對類自動生成equals 和 hashcode 方法不是一個好的選擇,因為超類也定義了欄位,這些欄位也需要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 註解會提示編譯錯誤

 

修改建議:需要為 @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

 

作者:cxuan

 

往期閱讀

1. SpringBoot內容聚合

2. 面試題內容聚合

3. 設計模式內容聚合

4. 排序演算法內容聚合

5. 多執行緒內容