一池颈、Lombok簡介
Lombok項目是一個Java庫芙贫,它會自動插入您的編輯器和構(gòu)建工具中,從而為您的Java增光添彩豌鸡。
永遠不要再編寫另一個getter或equals方法,一個帶有注釋的類將具有功能全面的生成器段标,自動執(zhí)行日志記錄變量等等涯冠。
簡而言之,就是自動幫您生成setter和getter逼庞,toString蛇更、equals等方法。
二、Lombok插件安裝
- 1.1 下載Intellij Idea Lombok插件
https://plugins.jetbrains.com/plugin/6317-lombok/versions
Lombok與Idea版本對應(yīng)表
選擇和版本匹配的插件派任,否則將可能出錯共耍;下載完成后,不需要解壓吨瞎。 -
1.2 Intellij Idea 安裝離線插件
從桌面安裝插件
安裝完成后痹兜,重新啟動即可。
二颤诀、Lombok依賴配置
maven倉庫查看版本并在pom.xml中添加依賴
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
三字旭、Java中使用Lombok
-
3.1 介紹
“樣板”是一個術(shù)語,用于描述在應(yīng)用程序的許多部分中很少改動就重復(fù)的代碼崖叫。 對Java語言最常提出的批評之一是在大多數(shù)項目中都可以找到這種類型的代碼遗淳。 這個問題通常是各種庫中設(shè)計決策的結(jié)果奔害,但由于語言本身的局限性而加劇了這一問題祝钢。 龍目島計劃(Project Lombok)旨在通過用簡單的注釋集代替某些最嚴重的違法者丽涩。
盡管使用批注來指示用法监婶,實現(xiàn)綁定甚至生成框架使用的代碼并不少見筛圆,但通常不會將其用于生成應(yīng)用程序直接使用的代碼卡骂。 部分原因是因為這樣做需要在開發(fā)時急切地處理批注崔慧。 龍目島項目就是這樣做的狠半。 通過集成到IDE中宰翅,Project Lombok能夠注入可供開發(fā)人員立即使用的代碼弃甥。 例如,僅將@Data批注添加到數(shù)據(jù)類中(如下所示)汁讼,就會在IDE中產(chǎn)生許多新方法:
Data_Annotation.png - 3.2 Lombok 注解
對于典型的Java項目來說淆攻,將數(shù)百行代碼專門用于定義簡單數(shù)據(jù)類所需的樣板并不少見。 這些類通常包含許多字段嘿架,這些字段的getter和setter以及equals和hashCode實現(xiàn)瓶珊。 在最簡單的情況下,Project Lombok可以將這些類簡化為必填字段和單個@Data批注耸彪。
當(dāng)然伞芹,最簡單的場景并不一定是開發(fā)人員每天面對的場景。 因此搜囱,Lombok項目中有許多注釋丑瞧,可以對類的結(jié)構(gòu)和行為進行更精細的控制。
3.2.1 @Getter 和@Setter
@ Getter和@Setter批注分別為字段生成getter和setter蜀肘。 正確生成的getter遵循布爾屬性的約定绊汹,因此對于任何布爾字段foo而言,它都是isFoo getter方法名稱而不是getFoo扮宠。 應(yīng)當(dāng)注意西乖,如果帶注釋的字段所屬的類包含與要生成的getter或setter同名的方法狐榔,則無論參數(shù)或返回類型如何,都不會生成相應(yīng)的方法获雕。
@Getter和@Setter注釋均帶有一個可選參數(shù)薄腻,以指定所生成方法的訪問級別。
注解代碼:
@Getter
@Setter
private boolean employed = true;
@Setter(AccessLevel.PROTECTED)
private String name;
等價于原Java代碼:
private boolean employed = true;
private String name;
public boolean isEmployed() {
return employed;
}
public void setEmployed(final boolean employed) {
this.employed = employed;
}
protected void setName(final String name) {
this.name = name;
}
3.2.2 @NonNull
@NonNull批注用于指示需要對相應(yīng)成員進行快速失敗的空檢查届案。 當(dāng)放置在Lombok為其生成setter方法的字段上時庵楷,將生成null檢查,如果提供null值楣颠,則將導(dǎo)致NullPointerException尽纽。 此外,如果Lombok正在為所屬類生成構(gòu)造函數(shù)童漩,則該字段將添加到構(gòu)造函數(shù)簽名中弄贿,并且空檢查將包含在生成的構(gòu)造函數(shù)代碼中。
此批注反映了在IntelliJ IDEA和FindBugs等中找到的@NotNull和@NonNull批注矫膨。 對于主題的這些變化差凹,Lombok與注解無關(guān)。 如果Lombok遇到任何帶有名稱@NotNull或@NonNull的任何注解的成員侧馅,它將通過生成適當(dāng)?shù)南鄳?yīng)代碼來兌現(xiàn)它危尿。 Lombok項目的作者進一步評論說,如果將這種類型的注解添加到Java中施禾,則Lombok版本將被刪除脚线。
注解代碼:
@Getter
@Setter
@NonNull
private List<Person> members;
等價于原Java代碼:
@NonNull
private List<Person> members;
public Family(@NonNull final List<Person> members) {
if (members == null) throw new java.lang.NullPointerException("members");
this.members = members;
}
@NonNull
public List<Person> getMembers() {
return members;
}
public void setMembers(@NonNull final List<Person> members) {
if (members == null) throw new java.lang.NullPointerException("members");
this.members = members;
}
3.2.3 @ToString
該注釋生成toString方法的實現(xiàn)。 默認情況下弥搞,所有非靜態(tài)字段都將以名稱/值對的形式包含在方法的輸出中。 如果需要渠旁,可以通過將注解參數(shù)includeFieldNames設(shè)置為false來抑制在輸出中包含屬性名稱攀例。
通過將特定字段的字段名稱包含在exclude參數(shù)中,可以從生成的方法的輸出中排除特定字段顾腊。 或者粤铭,可以使用of參數(shù)來僅列出輸出中所需的那些字段。 通過將callSuper參數(shù)設(shè)置為true杂靶,還可以包含超類的toString方法的輸出梆惯。
注解代碼:
@ToString(callSuper=true,exclude="someExcludedField")
public class Foo extends Bar {
private boolean someBoolean = true;
private String someStringField;
private float someExcludedField;
}
等價于原Java代碼:
public class Foo extends Bar {
private boolean someBoolean = true;
private String someStringField;
private float someExcludedField;
@java.lang.Override
public java.lang.String toString() {
return "Foo(super=" + super.toString() +
", someBoolean=" + someBoolean +
", someStringField=" + someStringField + ")";
}
}
3.2.4 @EqualsAndHashCode
這個類級別的注釋將使Lombok生成equals和hashCode方法,因為兩者通過hashCode契約本質(zhì)上聯(lián)系在一起吗垮。 默認情況下垛吗,兩種方法都將考慮類中任何非靜態(tài)或瞬態(tài)的字段。 與@ToString非常相似烁登,提供了exclude參數(shù)以防止將字段包含在生成的邏輯中怯屉。 也可以使用of參數(shù)來僅列出應(yīng)考慮的那些字段。
就像@ToString一樣,此注釋也有一個callSuper參數(shù)锨络。將其設(shè)置為true會導(dǎo)致equals通過在考慮當(dāng)前類中的字段之前從超類調(diào)用equals來驗證相等性赌躺。對于hashCode方法,它導(dǎo)致將超類的hashCode的結(jié)果并入哈希計算中羡儿。將callSuper設(shè)置為true時礼患,請確保父類中的equals方法正確處理實例類型檢查。如果父類檢查該類是否具有特定類型掠归,而不僅僅是兩個對象的類相同讶泰,則可能導(dǎo)致不良結(jié)果。如果超類使用的是Lombok生成的equals方法拂到,那么這不是問題痪署。但是,其他實現(xiàn)可能無法正確處理此情況兄旬。還要注意狼犯,當(dāng)類僅擴展Object時,無法將callSuper設(shè)置為true领铐,因為這將導(dǎo)致實例相等性檢查悯森,從而使字段比較短路。這是由于生成的方法調(diào)用了Object的equals實現(xiàn)绪撵,如果正在比較的兩個實例不是同一實例瓢姻,則返回false。結(jié)果音诈,在這種情況下幻碱,Lombok將生成編譯時錯誤。
注解代碼:
@EqualsAndHashCode(callSuper=true,exclude={"address","city","state","zip"})
public class Person extends SentientBeing {
enum Gender { Male, Female }
@NonNull private String name;
@NonNull private Gender gender;
private String ssn;
private String address;
private String city;
private String state;
private String zip;
}
等價于原Java代碼:
public class Person extends SentientBeing {
enum Gender {
/*public static final*/ Male /* = new Gender() */,
/*public static final*/ Female /* = new Gender() */;
}
@NonNull
private String name;
@NonNull
private Gender gender;
private String ssn;
private String address;
private String city;
private String state;
private String zip;
@java.lang.Override
public boolean equals(final java.lang.Object o) {
if (o == this) return true;
if (o == null) return false;
if (o.getClass() != this.getClass()) return false;
if (!super.equals(o)) return false;
final Person other = (Person)o;
if (this.name == null ? other.name != null : !this.name.equals(other.name)) return false;
if (this.gender == null ? other.gender != null : !this.gender.equals(other.gender)) return false;
if (this.ssn == null ? other.ssn != null : !this.ssn.equals(other.ssn)) return false;
return true;
}
@java.lang.Override
public int hashCode() {
final int PRIME = 31;
int result = 1;
result = result * PRIME + super.hashCode();
result = result * PRIME + (this.name == null ? 0 : this.name.hashCode());
result = result * PRIME + (this.gender == null ? 0 : this.gender.hashCode());
result = result * PRIME + (this.ssn == null ? 0 : this.ssn.hashCode());
return result;
}
}
3.2.5 @Data
@Data批注可能是Project Lombok工具集中最常用的批注细溅。 它結(jié)合了@ ToString褥傍,@ EqualsAndHashCode,@ Getter和@Setter的功能喇聊。 本質(zhì)上恍风,在類上使用@Data等同于使用默認的@ToString和@EqualsAndHashCode注釋類以及使用@Getter和@Setter注釋每個字段。 用@Data注釋類也會觸發(fā)Lombok的構(gòu)造函數(shù)生成誓篱。 這將添加一個公共構(gòu)造函數(shù)朋贬,該構(gòu)造函數(shù)將任何@NonNull或final字段用作參數(shù)。 這提供了普通Java對象(POJO)所需的一切窜骄。
盡管@Data非常有用锦募,但它不能提供與其他Lombok注釋相同的控制粒度。 為了覆蓋默認的方法生成行為啊研,請使用其他Lombok批注之一對類御滩,字段或方法進行批注鸥拧,并指定必要的參數(shù)值以實現(xiàn)所需的效果。
@Data確實提供了可用于生成靜態(tài)工廠方法的單個參數(shù)選項削解。 將staticConstructor參數(shù)的值設(shè)置為所需的方法名稱將使Lombok將生成的構(gòu)造函數(shù)設(shè)為私有富弦,并公開具有給定名稱的靜態(tài)工廠方法。
注解代碼:
@Data(staticConstructor="of")
public class Company {
private final Person founder;
private String name;
private List<Person> employees;
}
等價于原Java代碼:
public class Company {
private final Person founder;
private String name;
private List<Person> employees;
private Company(final Person founder) {
this.founder = founder;
}
public static Company of(final Person founder) {
return new Company(founder);
}
public Person getFounder() {
return founder;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
public List<Person> getEmployees() {
return employees;
}
public void setEmployees(final List<Person> employees) {
this.employees = employees;
}
@java.lang.Override
public boolean equals(final java.lang.Object o) {
if (o == this) return true;
if (o == null) return false;
if (o.getClass() != this.getClass()) return false;
final Company other = (Company)o;
if (this.founder == null ? other.founder != null : !this.founder.equals(other.founder)) return false;
if (this.name == null ? other.name != null : !this.name.equals(other.name)) return false;
if (this.employees == null ? other.employees != null : !this.employees.equals(other.employees)) return false;
return true;
}
@java.lang.Override
public int hashCode() {
final int PRIME = 31;
int result = 1;
result = result * PRIME + (this.founder == null ? 0 : this.founder.hashCode());
result = result * PRIME + (this.name == null ? 0 : this.name.hashCode());
result = result * PRIME + (this.employees == null ? 0 : this.employees.hashCode());
return result;
}
@java.lang.Override
public java.lang.String toString() {
return "Company(founder=" + founder + ", name=" + name + ", employees=" + employees + ")";
}
}
3.2.6 @Cleanup
@Cleanup批注可用于確保釋放分配的資源氛驮。 當(dāng)使用@Cleanup注釋局部變量時腕柜,任何后續(xù)代碼都將包裝在try / finally塊中,以確保在當(dāng)前作用域的末尾調(diào)用cleanup方法矫废。 默認情況下盏缤,@ Cleanup假定清除方法與輸入和輸出流一樣被命名為“ close”。 但是蓖扑,可以為注釋的value參數(shù)提供不同的方法名稱唉铜。 該注釋只能使用不帶參數(shù)的清除方法。
使用@Cleanup注釋時律杠,還需要注意一些注意事項潭流。 如果cleanup方法引發(fā)異常,它將搶占方法主體中引發(fā)的所有異常柜去。 這可能導(dǎo)致問題被掩埋的實際原因灰嫉,在選擇使用Project Lombok的資源管理時應(yīng)予以考慮。 此外嗓奢,隨著Java 7中自動資源管理的興起讼撒,這個特定的注釋可能相對較短。
注解代碼:
public void testCleanUp() {
try {
@Cleanup
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(new byte[] {'Y','e','s'});
System.out.println(baos.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
等價于原Java代碼:
public void testCleanUp() {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
baos.write(new byte[]{'Y', 'e', 's'});
System.out.println(baos.toString());
} finally {
baos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
3.2.6 @Synchronized
在方法上使用synchronized 關(guān)鍵字可能會導(dǎo)致不幸的后果股耽,任何從事多線程軟件開發(fā)的開發(fā)人員都可以證明根盒。 如果是實例方法,則synchronized 關(guān)鍵字將鎖定當(dāng)前對象(此對象)豺谈;對于靜態(tài)方法郑象,該關(guān)鍵字將鎖定該類對象。 這意味著開發(fā)人員無法控制代碼鎖定同一對象茬末,從而導(dǎo)致死鎖。 通常建議改為顯式地鎖定在專用于該目的的單獨對象上盖矫,并且不要以允許未經(jīng)請求的鎖定的方式公開丽惭。 為此,Project Lombok提供了@Synchronized批注辈双。
用@Synchronized注釋實例方法將提示Lombok生成一個名為$lock的私有鎖定字段责掏,該方法將在執(zhí)行之前在該字段上鎖定。 類似地湃望,以相同的方式注釋靜態(tài)方法將生成一個名為$lock的私有靜態(tài)對象换衬,以供靜態(tài)方法以相同方式使用痰驱。 可以通過為注釋的value參數(shù)提供字段名稱來指定其他鎖定對象。 提供字段名稱時瞳浦,開發(fā)人員必須定義屬性担映,因為Lombok不會生成該屬性。
注解代碼:
private DateFormat format = new SimpleDateFormat("MM-dd-YYYY");
@Synchronized
public String synchronizedFormat(Date date) {
return format.format(date);
}
等價于原Java代碼:
private final java.lang.Object $lock = new java.lang.Object[0];
private DateFormat format = new SimpleDateFormat("MM-dd-YYYY");
public String synchronizedFormat(Date date) {
synchronized ($lock) {
return format.format(date);
}
}
3.2.8 @SneakyThrows
@SneakyThrows可能是批評者最多的Project Lombok批注叫潦,因為它是對已檢查異常的直接攻擊蝇完。 在使用檢查異常方面存在很多分歧,許多開發(fā)人員認為這是一個失敗的實驗矗蕊。 這些開發(fā)人員將喜歡@SneakyThrows短蜕。 處于已檢查/未檢查異常范圍另一側(cè)的那些開發(fā)人員最有可能將其視為隱藏潛在問題。
如果在throws子句中未列出IllegalAccessException或某些父類傻咖,則拋出IllegalAccessException通常會生成“未處理的異撑竽В”錯誤:
當(dāng)使用@SneakyThrows注釋時,錯誤消失了卿操。
默認情況下警检,@ SneakyThrows將允許在不聲明throws子句的情況下引發(fā)任何已檢查的異常。 通過為注釋的value參數(shù)提供可拋出的類(Class)數(shù)組硬纤,可以將其限制為特定的一組異常解滓。
注解代碼:
@SneakyThrows
public void testSneakyThrows() {
throw new IllegalAccessException();
}
等價于原Java代碼:
public void testSneakyThrows() {
try {
throw new IllegalAccessException();
} catch (java.lang.Throwable $ex) {
throw lombok.Lombok.sneakyThrow($ex);
}
查看上面的代碼和Lombok.sneakyThrow(Throwable)的簽名,會使大多數(shù)人認為該異常已包裝在RuntimeException中并重新拋出筝家,但是事實并非如此洼裤。 scratchyThrow方法將永遠不會正常返回,而是將提供的throwable完全不變溪王。
- 3.3 優(yōu)缺點
與任何技術(shù)選擇一樣腮鞍,使用Lombok項目既有積極的影響,也有消極的影響莹菱。 將Lombok的注釋合并到項目中可以大大減少在IDE中生成或手動編寫的樣板代碼的行數(shù)移国。 這樣可以減少維護開銷,減少錯誤并增加可讀性的類道伟。
這并不是說在您的項目中使用Project Lombok注釋沒有不利之處迹缀。 Lombok項目主要旨在填補Java語言中的空白。 因此蜜徽,可能會發(fā)生語言更改祝懂,從而無法使用Lombok的注釋,例如添加了一流的屬性支持拘鞋。 此外砚蓬,當(dāng)與基于注釋的對象關(guān)系映射(ORM)框架結(jié)合使用時,數(shù)據(jù)類上注釋的數(shù)量可能開始變得笨拙盆色。 這在很大程度上被Lombok注釋所取代的代碼量所抵消灰蛙。 但是祟剔,那些避免頻繁使用注釋的人可能會選擇其他方式。
四摩梧、實例
Example Code -LombokExample.zip