1. 程式人生 > >Java小工具Lombok安裝和使用,讓JAVA程式碼更優雅

Java小工具Lombok安裝和使用,讓JAVA程式碼更優雅

Lombok簡介

Lombok專案通過新增“處理程式”,使java成為一種更為簡單的語言。作為一個Old Java Developer,我們都知道我們經常需要定義一系列的套路,比如定義如下的格式物件。

public class DataExample {
  private final String name;
  private int age;
  private double score;
  private String[] tags;
}

我們往往需要定義一系列的Get和Set方法最終展示形式如:

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

當然我們也可以簡化的辦法,第一種就是使用IDEA等IDE提供的一鍵生成的快捷鍵,第二種就是我們今天介紹的 Lombok專案:

@Data 
public class DataExample {
  private final String name;
  @Setter(AccessLevel.PACKAGE) 
  private int age;
  private double score;
  private String[] tags;
}

這樣就可以完成我們的需求,簡直是太棒了,僅僅需要幾個註解,我們就擁有了完整的GetSet方法,還包含了ToString等方法的生成。

Lombok安裝

使用lombok之前需要安裝lombok到IDE,否則IDE無法解析lombok註解;首先去官網下載最新的lonbok安裝包,網址:https://projectlombok.org/download  有兩種安裝方式:

1.      雙擊下載下來的 JAR 包安裝 lombok

安裝之前先關閉eclipse,然後雙擊下載的lombok.jar


如果像上圖所示提示找不到IDE,則需要手動選擇IDE,


出現上面的頁面時,點選Install/Update

開啟 Eclipse 的 About 頁面我們可以看見,如下圖所示,說明安裝成功


2.      eclipse 手動安裝 lombok

1)      將 lombok.jar 複製到 eclipse.ini 所在的資料夾目錄下

2)      開啟 eclipse.ini在最後面插入以下兩行並儲存:

        -Xbootclasspath/a:lombok.jar

        -javaagent:lombok.jar

3)    重啟eclipse

Lombok使用

1、在工程中引入lombok

在pom.xml中加入lombok依賴

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

2、Lombok註解介紹

1、@Data 註解:

包含了:@ToString,@EqualsAndHashCode, @Getter on all fields, @Setter on all non-final fields, [email protected]這些註解

註解在類上;提供類所有屬性的 getting 和 setting 方法,此外還提供了equals、canEqual、hashCode、toString 方法

@Data
public class Person {
  private final String name;
  private int age;
  private double score;
  private String[] tags;
}

相當於:


由上圖類結構可以看出,自動添加了set/get方法,同時還有一個帶有final欄位引數的建構函式

public Person(String name) {
    this.name = name;
}

還有toString、equals、canEqual、hashCode方法

@Override 
public String toString() {
    return "DataExample(" + this.getName() + ", " + this.getAge() + ", " + this.g	etScore() + ", " + 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;
  }

2、@value

@Value 是 @Data 的不可變版本,用作不可變

@Value
public class ValueExample {
  String name;
  @Wither(AccessLevel.PACKAGE) @NonFinal int age;
  double score;
  protected String[] tags;
  
  @ToString(includeFieldNames=true)
  @Value(staticConstructor="of")
  public static class Exercise<T> {
    String name;
    T value;
  }
}

翻譯過後為:

public final class ValueExample {
  private final String name;
  private int age;
  private final double score;
  protected final String[] tags;
  
  @java.beans.ConstructorProperties({"name", "age", "score", "tags"})
  public ValueExample(String name, int age, double score, String[] tags) {
    this.name = name;
    this.age = age;
    this.score = score;
    this.tags = tags;
  }
  
  public String getName() {
    return this.name;
  }
  
  public int getAge() {
    return this.age;
  }
  
  public double getScore() {
    return this.score;
  }
  
  public String[] getTags() {
    return this.tags;
  }
  
  @java.lang.Override
  public boolean equals(Object o) {
    if (o == this) return true;
    if (!(o instanceof ValueExample)) return false;
    final ValueExample other = (ValueExample)o;
    final Object this$name = this.getName();
    final Object other$name = other.getName();
    if (this$name == null ? other$name != null : !this$name.equals(other$name)) 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;
  }
  
  @java.lang.Override
  public int hashCode() {
    final int PRIME = 59;
    int result = 1;
    final Object $name = this.getName();
    result = result * PRIME + ($name == null ? 43 : $name.hashCode());
    result = result * PRIME + this.getAge();
    final long $score = Double.doubleToLongBits(this.getScore());
    result = result * PRIME + (int)($score >>> 32 ^ $score);
    result = result * PRIME + Arrays.deepHashCode(this.getTags());
    return result;
  }
  
  @java.lang.Override
  public String toString() {
    return "ValueExample(name=" + getName() + ", age=" + getAge() + ", score=" + getScore() + ", tags=" + Arrays.deepToString(getTags()) + ")";
  }
  
  ValueExample withAge(int age) {
    return this.age == age ? this : new ValueExample(name, age, score, tags);
  }
  
  public static final 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;
    }
    
    @java.lang.Override
    public boolean equals(Object o) {
      if (o == this) return true;
      if (!(o instanceof ValueExample.Exercise)) return false;
      final Exercise<?> other = (Exercise<?>)o;
      final Object this$name = this.getName();
      final Object other$name = other.getName();
      if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false;
      final Object this$value = this.getValue();
      final Object other$value = other.getValue();
      if (this$value == null ? other$value != null : !this$value.equals(other$value)) return false;
      return true;
    }
    
    @java.lang.Override
    public int hashCode() {
      final int PRIME = 59;
      int result = 1;
      final Object $name = this.getName();
      result = result * PRIME + ($name == null ? 43 : $name.hashCode());
      final Object $value = this.getValue();
      result = result * PRIME + ($value == null ? 43 : $value.hashCode());
      return result;
    }
    
    @java.lang.Override
    public String toString() {
      return "ValueExample.Exercise(name=" + getName() + ", value=" + getValue() + ")";
    }
  }
}

3、@setter、 @Getter

註解在屬性上;為屬性提供 setting 、getting 方法

public class GetterSetterExample {

  @Getter @Setter private int age = 10;
  
  @Setter(AccessLevel.PROTECTED) private String name;
  
  @Override public String toString() {
    return String.format("%s (age: %d)", name, age);
  }
}

翻譯後:

public class GetterSetterExample {

  private int age = 10;

  private String name;
  
  @Override public String toString() {
    return String.format("%s (age: %d)", name, age);
  }
  
  public int getAge() {
    return age;
  }
  
  public void setAge(int age) {
    this.age = age;
  }
  
  protected void setName(String name) {
    this.name = name;
  }
}

4、@Log

註解在類上;為類提供一個 屬性為log 日誌物件

該註解,有以下幾種可以選擇:

@CommonsLog
Creates private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(LogExample.class);
@JBossLog
Creates private static final org.jboss.logging.Logger log = org.jboss.logging.Logger.getLogger(LogExample.class);
@Log
Creates private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LogExample.class.getName());
@Log4j
Creates private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(LogExample.class);
@Log4j2
Creates private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class);
@Slf4j
Creates private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExample.class);
@XSlf4j
Creates private static final org.slf4j.ext.XLogger log = org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class);

5、@Getter(lazy = true)

使用了getter這個annotation可以在實際使用到cached的時候生成cached,同時,Lombok會自動去管理執行緒安全的問題,不會存在重複賦值的問題。如下程式碼所示

public class GetterLazyExample {
  @Getter(lazy = true)
  private final double[] cached = expensive();
  public double[] expensive() {
    double[] result = new double[10000000];
    for (int i = 0; i < result.length; i++) {
      result[i] = Math.abs(i);
    }
    return result;
  }
}

6、@ToString

生成toString()方法,預設情況下,它會按順序(以逗號分隔)列印你的類名稱以及每個欄位。可以這樣設定不包含哪些欄位@ToString(exclude = "id") / @ToString(exclude = {"id","name"})

如果繼承的有父類的話,可以設定callSuper 讓其呼叫父類的toString()方法,例如:@ToString(callSuper = true)
@ToString(exclude="id")
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();
  }
  
  @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 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) + ")";
  }
}
@ToString(exclude = {"id","name"})
public class User {
  private Integer id;
  private String name;
  private String phone;
}

生成的toString方法為:

public String toString(){
  return "User(phone=" + phone + ")";
}

7、@NoArgsConstructor、@AllArgsConstructor、@RequiredArgsConstructor

@NoArgsConstructor生成一個無參構造方法。當類中有final欄位沒有被初始化時,編譯器會報錯,此時可用@NoArgsConstructor(force = true),然後就會為沒有初始化的final欄位設定預設值 0 / false / null。對於具有約束的欄位(例如@NonNull欄位),不會生成檢查或分配,因此請注意,正確初始化這些欄位之前,這些約束無效。

@AllArgsConstructor註解在類上;為類提供一個全參的構造方法

@RequiredArgsConstructor會生成構造方法(可能帶引數也可能不帶引數),如果帶引數,這引數只能是以final修飾的未經初始化的欄位,或者是以@NonNull註解的未經初始化的欄位
@RequiredArgsConstructor(staticName = "of")會生成一個of()的靜態方法,並把構造方法設定為私有的
8、@Cleanup 
public static void main(String[] args) throws IOException {
    @Cleanup InputStream in = new FileInputStream(args[0]);
    @Cleanup OutputStream out = new FileOutputStream(args[1]);
    byte[] b = new byte[10000];
    while (true) {
      int r = in.read(b);
      if (r == -1) break;
      out.write(b, 0, r);
  	}
  }

相當於:

public static void main(String[] args) throws IOException {
    InputStream in = new FileInputStream(args[0]);
    try {
      OutputStream out = new FileOutputStream(args[1]);
      try {
        byte[] b = new byte[10000];
        while (true) {
          int r = in.read(b);
          if (r == -1) break;
          out.write(b, 0, r);
        }
      } finally {
        if (out != null) {
          out.close();
        }
      }

@Cleanup 存在一個小問題:官網給出了提示,如果你的程式碼中出現了異常,那麼會觸發cleanup方法丟擲異常,導致把原始異常吞掉,這樣就導致你在上層不知道發生了什麼事情,這個事情很嚴重啊,但是如果說你是在呼叫close方法的時候出了異常,那麼Cleanup這個annotation是不會把異常吞掉的。同時,官網也指出,作業也沒有找到更好的方式去解決這個問題,如果他們找到了會立刻fix這個問題,否則也就只能等待Java 有可能在Update的時候出現新的解決方案。

java7以後建議使用java自帶的try-with-resources語句



9、 @Builder 

@Builder註解可以自動生成build()方法,構造一個例項屬性不需要單獨set 


@Builder
@Data
public class BuilderExample {
 private int age;
 @Singular private Set<String> names;
 
 public static void main(String[] args) throws IOException {
   BuilderExample aBuilderExample = BuilderExample.builder().name("1").name("1").name("zs").build();
   System.out.println(aBuilderExample);
 }
}

上面的例子輸出結果為:


其中@Singular註解用於註釋集合欄位,該欄位的命名必須採用複數的形式,在使用build方法的時候,改欄位的單數形式可以直接當做非集合的方式進行賦值。

builder是現在比較推崇的一種構建值物件的方式。

可以看下官方示例,對比一下就都明白了

@Builder
public class BuilderExample {
 private String name;
 private int age;
 @Singular private Set<String> occupations;
}

翻譯後為

class BuilderExample {
  private String name;
  private int age;
  private Set<String> occupations;

  BuilderExample(String name, int age, Set<String> occupations) {
      this.name = name;
      this.age = age;
      this.occupations = occupations;
  }

  public static BuilderExampleBuilder builder() {
      return new BuilderExampleBuilder();
  }

  public static class BuilderExampleBuilder {
      private String name;
      private int age;
      private java.util.ArrayList<String> occupations;

      BuilderExampleBuilder() {
      }

      public BuilderExampleBuilder name(String name) {
          this.name = name;
          return this;
      }

      public BuilderExampleBuilder age(int age) {
          this.age = age;
          return this;
      }

      public BuilderExampleBuilder occupation(String occupation) {
          if (this.occupations == null) {
              this.occupations = new java.util.ArrayList<String>();
          }

          this.occupations.add(occupation);
          return this;
      }

      public BuilderExampleBuilder occupations(Collection<? extends String> occupations) {
          if (this.occupations == null) {
              this.occupations = new java.util.ArrayList<String>();
          }

          this.occupations.addAll(occupations);
          return this;
      }

      public BuilderExampleBuilder clearOccupations() {
          if (this.occupations != null) {
              this.occupations.clear();
          }

          return this;
      }

      public BuilderExample build() {
          // complicated switch statement to produce a compact properly sized immutable set omitted.
          // go to https://projectlombok.org/features/Singular-snippet.html to see it.
          Set<String> occupations = ...;
          return new BuilderExample(name, age, occupations);
      }

      @java.lang.Override
      public String toString() {
          return "BuilderExample.BuilderExampleBuilder(name = " + this.name + ", age = " + this.age + ", occupations = " + this.occupations + ")";
      }
  }
}

10、@NonNull

相當於自動加上是否為null的判斷


public class NonNullExample {
  private String name;  
  public NonNullExample(@NonNull Person person) {
      this.name = person.getName();
  }
}

相當於:

public class NonNullExample{
  private String name;  
  public NonNullExample(Person person) {
    if (person == null) {
      throw new NullPointerException("person");
    }
    this.name = person.getName();
  }
}

Lombok原理

說道 Lombok,我們就得去提到 JSR 269: Pluggable Annotation Processing API (https://www.jcp.org/en/jsr/detail?id=269) 。JSR 269 之前我們也有註解這樣的神器,可是我們比如想要做什麼必須使用反射,反射的方法侷限性較大。首先,它必須定義@Retention為RetentionPolicy.RUNTIME,只能在執行時通過反射來獲取註解值,使得執行時程式碼效率降低。其次,如果想在編譯階段利用註解來進行一些檢查,對使用者的某些不合理程式碼給出錯誤報告,反射的使用方法就無能為力了。而 JSR 269 之後我們可以在 Javac的編譯期利用註解做這些事情。所以我們發現核心的區分是在 執行期 還是 編譯期。


從上圖可知,Annotation Processing 是在解析和生成之間的一個步驟。


上圖是 Lombok 處理流程,在Javac 解析成抽象語法樹之後(AST), Lombok 根據自己的註解處理器,動態的修改 AST,增加新的節點(所謂程式碼),最終通過分析和生成位元組碼。