Lombok介紹扳埂、使用方法和總結(jié)

文章來源公眾號:猿人谷

1 Lombok背景介紹

官方介紹如下:

Project Lombok makes java a spicier language by adding 'handlers' that know how to build and compile simple, boilerplate-free, not-quite-java code.

大致意思是Lombok通過增加一些“處理程序”恩溅,可以讓java變得簡潔、快速寺酪。

2 Lombok使用方法

Lombok能以簡單的注解形式來簡化java代碼坎背,提高開發(fā)人員的開發(fā)效率。例如開發(fā)中經(jīng)常需要寫的javabean寄雀,都需要花時間去添加相應(yīng)的getter/setter得滤,也許還要去寫構(gòu)造器、equals等方法盒犹,而且需要維護懂更,當屬性多時會出現(xiàn)大量的getter/setter方法,這些顯得很冗長也沒有太多技術(shù)含量急膀,一旦修改屬性沮协,就容易出現(xiàn)忘記修改對應(yīng)方法的失誤。

Lombok能通過注解的方式卓嫂,在編譯時自動為屬性生成構(gòu)造器慷暂、getter/setter、equals命黔、hashcode呜呐、toString方法。出現(xiàn)的神奇就是在源碼中沒有g(shù)etter和setter方法悍募,但是在編譯生成的字節(jié)碼文件中有g(shù)etter和setter方法蘑辑。這樣就省去了手動重建這些代碼的麻煩,使代碼看起來更簡潔些坠宴。

Lombok的使用跟引用jar包一樣洋魂,可以在官網(wǎng)(https://projectlombok.org/download)下載jar包,也可以使用maven添加依賴:

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

接下來我們來分析Lombok中注解的具體用法。

2.1 @Data

@Data注解在類上副砍,會為類的所有屬性自動生成setter/getter衔肢、equals、canEqual豁翎、hashCode角骤、toString方法,如為final屬性心剥,則不會為該屬性生成setter方法邦尊。

官方實例如下:

import lombok.AccessLevel;
import lombok.Setter;
import lombok.Data;
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,則實現(xiàn)如下:

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

2.2 @Getter/@Setter

如果覺得@Data太過殘暴(因為@Data集合了@ToString优烧、@EqualsAndHashCode蝉揍、@Getter/@Setter、@RequiredArgsConstructor的所有特性)不夠精細畦娄,可以使用@Getter/@Setter注解又沾,此注解在屬性上,可以為相應(yīng)的屬性自動生成Getter/Setter方法熙卡,示例如下:

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

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

如果不使用Lombok:

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

2.3 @NonNull

該注解用在屬性或構(gòu)造器上杖刷,Lombok會生成一個非空的聲明,可用于校驗參數(shù)再膳,能幫助避免空指針挺勿。

示例如下:

import lombok.NonNull;

public class NonNullExample extends Something {
  private String name;
  
  public NonNullExample(@NonNull Person person) {
    super("Hello");
    this.name = person.getName();
  }
}

不使用Lombok:

import lombok.NonNull;

public class NonNullExample extends Something {
  private String name;
  
  public NonNullExample(@NonNull Person person) {
    super("Hello");
    if (person == null) {
      throw new NullPointerException("person");
    }
    this.name = person.getName();
  }
}

2.4 @Cleanup

該注解能幫助我們自動調(diào)用close()方法,很大的簡化了代碼喂柒。

示例如下:

import lombok.Cleanup;
import java.io.*;

public class CleanupExample {
  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);
    }
  }
}

如不使用Lombok不瓶,則需如下:

import java.io.*;

public class CleanupExample {
  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();
        }
      }
    } finally {
      if (in != null) {
        in.close();
      }
    }
  }
}

2.5 @EqualsAndHashCode

默認情況下,會使用所有非靜態(tài)(non-static)和非瞬態(tài)(non-transient)屬性來生成equals和hasCode灾杰,也能通過exclude注解來排除一些屬性蚊丐。

示例如下:

import lombok.EqualsAndHashCode;

@EqualsAndHashCode(exclude={"id", "shape"})
public class EqualsAndHashCodeExample {
  private transient int transientVar = 10;
  private String name;
  private double score;
  private Shape shape = new Square(5, 10);
  private String[] tags;
  private int id;
  
  public String getName() {
    return this.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;
    }
  }
}

2.6 @ToString

類使用@ToString注解,Lombok會生成一個toString()方法艳吠,默認情況下麦备,會輸出類名、所有屬性(會按照屬性定義順序)昭娩,用逗號來分割凛篙。

通過將includeFieldNames參數(shù)設(shè)為true,就能明確的輸出toString()屬性栏渺。這一點是不是有點繞口呛梆,通過代碼來看會更清晰些。

使用Lombok的示例:

import lombok.ToString;

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

不使用Lombok的示例如下:

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) + ")";
  }
}

2.7 @NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor

無參構(gòu)造器磕诊、部分參數(shù)構(gòu)造器填物、全參構(gòu)造器纹腌。Lombok沒法實現(xiàn)多種參數(shù)構(gòu)造器的重載。

Lombok示例代碼如下:

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

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

不使用Lombok的示例如下:

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

3 Lombok工作原理分析

會發(fā)現(xiàn)在Lombok使用的過程中滞磺,只需要添加相應(yīng)的注解升薯,無需再為此寫任何代碼。自動生成的代碼到底是如何產(chǎn)生的呢击困?

核心之處就是對于注解的解析上涎劈。JDK5引入了注解的同時,也提供了兩種解析方式沛励。

  • 運行時解析

運行時能夠解析的注解责语,必須將@Retention設(shè)置為RUNTIME,這樣就可以通過反射拿到該注解目派。java.lang,reflect反射包中提供了一個接口AnnotatedElement,該接口定義了獲取注解信息的幾個方法胁赢,Class企蹭、Constructor、Field智末、Method谅摄、Package等都實現(xiàn)了該接口,對反射熟悉的朋友應(yīng)該都會很熟悉這種解析方式系馆。

  • 編譯時解析

編譯時解析有兩種機制送漠,分別簡單描述下:

1)Annotation Processing Tool

apt自JDK5產(chǎn)生,JDK7已標記為過期由蘑,不推薦使用闽寡,JDK8中已徹底刪除,自JDK6開始尼酿,可以使用Pluggable Annotation Processing API來替換它爷狈,apt被替換主要有2點原因:

  • api都在com.sun.mirror非標準包下
  • 沒有集成到j(luò)avac中,需要額外運行

2)Pluggable Annotation Processing API

JSR 269自JDK6加入裳擎,作為apt的替代方案涎永,它解決了apt的兩個問題,javac在執(zhí)行的時候會調(diào)用實現(xiàn)了該API的程序鹿响,這樣我們就可以對編譯器做一些增強羡微,這時javac執(zhí)行的過程如下:

123

Lombok本質(zhì)上就是一個實現(xiàn)了“JSR 269 API”的程序。在使用javac的過程中惶我,它產(chǎn)生作用的具體流程如下:

  1. javac對源代碼進行分析妈倔,生成了一棵抽象語法樹(AST)
  2. 運行過程中調(diào)用實現(xiàn)了“JSR 269 API”的Lombok程序
  3. 此時Lombok就對第一步驟得到的AST進行處理,找到@Data注解所在類對應(yīng)的語法樹(AST)指孤,然后修改該語法樹(AST)启涯,增加getter和setter方法定義的相應(yīng)樹節(jié)點
  4. javac使用修改后的抽象語法樹(AST)生成字節(jié)碼文件贬堵,即給class增加新的節(jié)點(代碼塊)

拜讀了Lombok源碼,對應(yīng)注解的實現(xiàn)都在HandleXXX中结洼,比如@Getter注解的實現(xiàn)時HandleGetter.handle()黎做。還有一些其它類庫使用這種方式實現(xiàn),比如Google Auto松忍、Dagger等等蒸殿。

4. Lombok的優(yōu)缺點

優(yōu)點:

  1. 能通過注解的形式自動生成構(gòu)造器、getter/setter鸣峭、equals宏所、hashcode、toString等方法摊溶,提高了一定的開發(fā)效率
  2. 讓代碼變得簡潔爬骤,不用過多的去關(guān)注相應(yīng)的方法
  3. 屬性做修改時,也簡化了維護為這些屬性所生成的getter/setter方法等

缺點:

  1. 不支持多種參數(shù)構(gòu)造器的重載
  2. 雖然省去了手動創(chuàng)建getter/setter方法的麻煩莫换,但大大降低了源代碼的可讀性和完整性霞玄,降低了閱讀源代碼的舒適度

5. 總結(jié)

Lombok雖然有很多優(yōu)點,但Lombok更類似于一種IDE插件拉岁,項目也需要依賴相應(yīng)的jar包坷剧。Lombok依賴jar包是因為編譯時要用它的注解,為什么說它又類似插件喊暖?因為在使用時惫企,eclipse或IntelliJ IDEA都需要安裝相應(yīng)的插件,在編譯器編譯時通過操作AST(抽象語法樹)改變字節(jié)碼生成陵叽,變向的就是說它在改變java語法狞尔。它不像spring的依賴注入或者mybatis的ORM一樣是運行時的特性,而是編譯時的特性咨跌。這里我個人最感覺不爽的地方就是對插件的依賴沪么!因為Lombok只是省去了一些人工生成代碼的麻煩,但IDE都有快捷鍵來協(xié)助生成getter/setter等方法锌半,也非常方便禽车。

知乎上有位大神發(fā)表過對Lombok的一些看法:

這是一種低級趣味的插件,不建議使用刊殉。JAVA發(fā)展到今天殉摔,各種插件層出不窮,如何甄別各種插件的優(yōu)劣记焊?能從架構(gòu)上優(yōu)化你的設(shè)計的逸月,能提高應(yīng)用程序性能的 ,
實現(xiàn)高度封裝可擴展的...遍膜, 像lombok這種碗硬,像這種插件瓤湘,已經(jīng)不僅僅是插件了,改變了你如何編寫源碼恩尾,事實上弛说,少去了代碼你寫上去又如何? 
如果JAVA家族到處充斥這樣的東西翰意,那只不過是一坨披著金屬顏色的屎木人,遲早會被其它的語言取代。

雖然話糙但理確實不糙冀偶,試想一個項目有非常多類似Lombok這樣的插件醒第,個人覺得真的會極大的降低閱讀源代碼的舒適度。

雖然非常不建議在屬性的getter/setter寫一些業(yè)務(wù)代碼进鸠,但在多年項目的實戰(zhàn)中稠曼,有時通過給getter/setter加一點點業(yè)務(wù)代碼,能極大的簡化某些業(yè)務(wù)場景的代碼堤如。所謂取舍蒲列,也許就是這時的舍棄一定的規(guī)范,取得極大的方便搀罢。

我現(xiàn)在非常堅信一條理念,任何編程語言或插件侥猩,都僅僅只是工具而已榔至,即使工具再強大也在于用的人,就如同小米加步槍照樣能贏飛機大炮的道理一樣欺劳。結(jié)合具體業(yè)務(wù)場景和項目實際情況唧取,無需一味追求高大上的技術(shù),適合的才是王道划提。

Lombok有它的得天獨厚的優(yōu)點枫弟,也有它避之不及的缺點,熟知其優(yōu)缺點鹏往,在實戰(zhàn)中靈活運用才是王道淡诗。

參考:

https://projectlombok.org/features/

https://github.com/rzwitserloot/lombok?spm=a2c4e.11153940.blogcont59972.5.2aeb6d32hayLHv

https://www.zhihu.com/question/42348457

https://blog.csdn.net/ghsau/article/details/52334762

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市伊履,隨后出現(xiàn)的幾起案子韩容,更是在濱河造成了極大的恐慌,老刑警劉巖唐瀑,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件群凶,死亡現(xiàn)場離奇詭異,居然都是意外死亡哄辣,警方通過查閱死者的電腦和手機请梢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門赠尾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人毅弧,你說我怎么就攤上這事气嫁。” “怎么了形真?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵杉编,是天一觀的道長。 經(jīng)常有香客問我咆霜,道長邓馒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任蛾坯,我火速辦了婚禮光酣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘脉课。我一直安慰自己救军,他們只是感情好,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布倘零。 她就那樣靜靜地躺著唱遭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪呈驶。 梳的紋絲不亂的頭發(fā)上拷泽,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天,我揣著相機與錄音袖瞻,去河邊找鬼司致。 笑死,一個胖子當著我的面吹牛聋迎,可吹牛的內(nèi)容都是我干的脂矫。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼霉晕,長吁一口氣:“原來是場噩夢啊……” “哼庭再!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起娄昆,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤佩微,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后萌焰,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體哺眯,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年扒俯,在試婚紗的時候發(fā)現(xiàn)自己被綠了奶卓。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片一疯。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖夺姑,靈堂內(nèi)的尸體忽然破棺而出墩邀,到底是詐尸還是另有隱情,我是刑警寧澤盏浙,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布眉睹,位于F島的核電站,受9級特大地震影響废膘,放射性物質(zhì)發(fā)生泄漏竹海。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一丐黄、第九天 我趴在偏房一處隱蔽的房頂上張望斋配。 院中可真熱鬧,春花似錦灌闺、人聲如沸艰争。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽甩卓。三九已至,卻和暖如春蕉斜,著一層夾襖步出監(jiān)牢的瞬間猛频,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工蛛勉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人睦柴。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓诽凌,卻偏偏與公主長得像,于是被迫代替她去往敵國和親坦敌。 傳聞我的和親對象是個殘疾皇子侣诵,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容