極簡代碼神器: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. 多線程內容聚合


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM