Contents
介紹
Project Lombok是一個(gè)java庫(kù)码倦,它可以自動(dòng)插入你的編輯器并構(gòu)建工具,增強(qiáng)你的java代碼。
永遠(yuǎn)不要再寫另一個(gè)getter或equals方法催植,使用一個(gè)注解榨呆,您的類具有一個(gè)功能齊全的構(gòu)建器凡纳,自動(dòng)化您的日志記錄變量等等:
安裝
idea 安裝lombok
該Jetbrains的IntelliJ IDEA的編輯與lombok兼容录肯。
添加Lombok IntelliJ插件以添加對(duì)IntelliJ的lombok支持:
- 去
File > Settings > Plugins
- 點(diǎn)擊
Browse repositories...
- 搜索
Lombok Plugin
- 點(diǎn)擊
Install plugin
- 重啟IntelliJ IDEA
Lombok 注解
典型的Java項(xiàng)目將數(shù)百行代碼用于定義簡(jiǎn)單數(shù)據(jù)類所需的樣板文件并不罕見(jiàn)屏鳍。這些類通常包含這些字段的若干字段溉仑,getter和setter挖函,以及equals和 hashCode實(shí)施方式。在最簡(jiǎn)單的場(chǎng)景中浊竟,Lombok項(xiàng)目可以將這些類減少到必需的字段和單個(gè)@Data注解怨喘。
當(dāng)然,最簡(jiǎn)單的場(chǎng)景不一定是開發(fā)人員日常所面臨的場(chǎng)景逐沙。出于這個(gè)原因哲思,Project Lombok中有許多注解允許對(duì)類的結(jié)構(gòu)和行為進(jìn)行更細(xì)粒度的控制。
@Getter and @Setter
@Getter
和@Setter
注解分別為字段生成getter和setter吩案。生成的getter方法正確地遵循了布爾屬性的約定棚赔,從而為任何布爾字段foo生成一個(gè)isFoo
getter方法名,而不是getFoo
徘郭。
需要注意的是靠益,如果帶注解字段所屬的類包含與要生成的getter或setter同名的方法,無(wú)論參數(shù)或返回類型如何残揉,都不會(huì)生成相應(yīng)的方法胧后。
@Getter
和@Setter
注解都采用一個(gè)可選參數(shù)來(lái)指定生成方法的訪問(wèn)級(jí)別。
Lombok注解代碼:
@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;
}
@NonNull
@NonNull
注釋用于指示對(duì)對(duì)應(yīng)成員進(jìn)行快速失敗null檢查的需要抱环。當(dāng)放置在Lombok正在為其生成setter方法的字段上時(shí)壳快,將生成null檢查纸巷,如果提供了null值,將導(dǎo)致NullPointerException
眶痰。此外瘤旨,如果Lombok正在為擁有的類生成構(gòu)造函數(shù),那么字段將被添加到構(gòu)造函數(shù)簽名中竖伯,null檢查將包含在生成的構(gòu)造函數(shù)代碼中存哲。
IntelliJ IDEA和FindBugs等中的 注解鏡像@NotNull
和@NonNull
注解。對(duì)于主題的這些變化七婴,Lombok是注解不可知的祟偷。如果Lombok遇到任何使用該名稱的注解注解的成員,@NotNull
或者@NonNull
它將通過(guò)生成適當(dāng)?shù)南鄳?yīng)代碼來(lái)遵守它打厘。Project Lombok的作者進(jìn)一步評(píng)論說(shuō)修肠,如果將此類型的注解添加到Java,則Lombok版本將被刪除婚惫。
Family的Lombok注解代碼:
@Getter @Setter @NonNull
private List<Person> members;
等價(jià)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;
}
@ToString
此注釋生成toString方法的實(shí)現(xiàn)氛赐。默認(rèn)情況下,任何非靜態(tài)字段都將以名稱-值對(duì)的形式包含在方法的輸出中先舷。如果需要艰管,可以通過(guò)將注釋參數(shù)includeFieldNames
設(shè)置為false
來(lái)排除在輸出中包含屬性名。
通過(guò)在exclude 參數(shù)中包含其字段名稱蒋川,可以從生成的方法的輸出中排除特定字段牲芋。或者捺球,該of
參數(shù)可用于僅列出輸出中所需的那些字段缸浦。toString
通過(guò)將callSuper
參數(shù) 設(shè)置為,還可以包括超類方法的輸出true
氮兵。
Lombok 注解代碼:
@ToString(callSuper=true,exclude="someExcludedField")
public class Foo extends Bar {
private boolean someBoolean = true;
private String someStringField;
private float someExcludedField;
}
等價(jià)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 + ")";
}
}
@EqualsAndHashCode
這個(gè)類級(jí)注解將導(dǎo)致Lombok生成兩者 equals
和hashCode
方法裂逐,因?yàn)檫@兩者是由hashCode
契約本質(zhì)上綁定在一起的。默認(rèn)情況下泣栈,兩個(gè)方法都將考慮類中非靜態(tài)或瞬態(tài)的任何字段卜高。很像@ToString
,exclude
提供參數(shù)是為了防止字段包含在生成的邏輯中南片。也可以使用該 of參數(shù)僅列出應(yīng)考慮的那些字段掺涛。
同樣@ToString
,callSuper
這個(gè)注解有一個(gè)參數(shù)疼进。將其設(shè)置為true將導(dǎo)致 在考慮當(dāng)前類中的字段之前equals
通過(guò)調(diào)用equals
超類來(lái)驗(yàn)證相等性薪缆。對(duì)于該hashCode
方法,它導(dǎo)致在hashCode
計(jì)算散列時(shí)結(jié)合超類的結(jié)果伞广。設(shè)置callSuper為true時(shí)拣帽,請(qǐng)注意確保父類中的equals方法正確處理實(shí)例類型檢查疼电。如果父類檢查該類是否屬于特定類型而不僅僅是兩個(gè)對(duì)象的類相同,則可能導(dǎo)致不希望的結(jié)果减拭。如果超類使用生成的Lombok equals方法澜沟,這不是問(wèn)題。但是峡谊,其他實(shí)現(xiàn)可能無(wú)法正確處理此情況。另請(qǐng)注意刊苍,callSuper
當(dāng)類僅擴(kuò)展時(shí)Object既们,無(wú)法設(shè)置 為true ,因?yàn)檫@會(huì)導(dǎo)致實(shí)例相等性檢查使字段比較短路正什。這是由于生成的方法調(diào)用了equals實(shí)現(xiàn) Object
啥纸,如果被比較的兩個(gè)實(shí)例不是同一個(gè)實(shí)例,則返回false婴氮。因此斯棒,在這種情況下,Lombok將生成編譯時(shí)錯(cuò)誤主经。
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;
}
等價(jià)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;
}
}
@Data
該@Data
注解很可能在項(xiàng)目lombok工具箱中最常用的注解荣暮。它結(jié)合的功能@ToString
,@EqualsAndHashCode
罩驻,@Getter
和@Setter
穗酥。本質(zhì)上,使用 @Data
的一類是一樣的帶有默認(rèn)注解類@ToString
和@EqualsAndHashCode
以及與兩個(gè)注解每個(gè)字段@Getter 和@Setter
惠遏。對(duì)類進(jìn)行注解@Data
也會(huì)觸發(fā)Lombok的構(gòu)造函數(shù)生成砾跃。這會(huì)添加一個(gè)公共構(gòu)造函數(shù),它將任何@NonNull
或final
字段作為參數(shù)节吮。這提供了普通舊Java對(duì)象(PO??JO)所需的一切抽高。
雖然@Data
非常有用,但它不提供與其他Lombok注解相同的控制粒度透绩。要覆蓋默認(rèn)方法生成行為翘骂,請(qǐng)使用其他Lombok注解之一注解類,字段或方法渺贤,并指定必要的參數(shù)值以實(shí)現(xiàn)所需的效果雏胃。
@Data
確實(shí)提供了一個(gè)可用于生成靜態(tài)工廠方法的參數(shù)選項(xiàng)。將staticConstructor參數(shù)的值設(shè)置為 所需的方法名稱將導(dǎo)致Lombok將生成的構(gòu)造函數(shù)設(shè)置為private志鞍,并公開給定名稱的靜態(tài)工廠方法瞭亮。
Lombok 注解代碼:
@Data(staticConstructor="of")
public class Company {
private final Person founder;
private String name;
private List<Person> employees;
}
等價(jià)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 + ")";
}
}
@Cleanup
該@Cleanup
注解可以用來(lái)保證分配的資源被釋放。當(dāng)使用帶注解的局部變量時(shí)@Cleanup
固棚,任何后續(xù)代碼都包含在一個(gè) try/finally
塊中统翩,該塊保證在當(dāng)前作用域的末尾調(diào)用cleanup方法仙蚜。默認(rèn)情況下,@Cleanup
假設(shè)清理方法命名為“close”厂汗,與輸入和輸出流一樣委粉。但是,可以為注解的value參數(shù)提供不同的方法名稱娶桦。只有不帶參數(shù)的清理方法才能與此注解一起使用贾节。
使用@Cleanup
注解時(shí)還需要注意一點(diǎn)。如果清理方法拋出異常衷畦,它將搶占方法體中引發(fā)的任何異常栗涂。這可能導(dǎo)致問(wèn)題的實(shí)際原因被掩蓋,并且在選擇使用Project Lombok的資源管理時(shí)應(yīng)該考慮到這一點(diǎn)祈争。此外斤程,隨著Java 7中的自動(dòng)資源管理,這個(gè)特定的注解可能相對(duì)短暫菩混。
Lombok注解代碼:
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();
}
}
等價(jià)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();
}
}
@Synchronized
在一個(gè)方法上使用synchronized
關(guān)鍵字可能會(huì)導(dǎo)致不好的結(jié)果忿墅,任何一個(gè)開發(fā)多線程軟件的開發(fā)人員都可以證明這一點(diǎn)。synchronized
關(guān)鍵字在實(shí)例方法或靜態(tài)方法的類對(duì)象中會(huì)鎖定當(dāng)前對(duì)象沮峡。這意味著開發(fā)人員控制之外的代碼有可能鎖定同一個(gè)對(duì)象疚脐,從而導(dǎo)致死鎖。通常建議在單獨(dú)的對(duì)象上顯式鎖定邢疙,該對(duì)象專門用于此目的亮曹,而不是以允許非請(qǐng)求鎖定的方式公開。Project Lombok正是為此目的提供了@Synchronized
注解秘症。
用@Synchronized
注解一個(gè)實(shí)例方法將提示Lombok生成一個(gè)名為LOCK`的私有靜態(tài)對(duì)象役耕,以便以相同的方式使用〈狭可以通過(guò)為注解的值參數(shù)提供字段名來(lái)指定不同的鎖定對(duì)象瞬痘。當(dāng)提供字段名時(shí),開發(fā)人員必須定義該屬性板熊,因?yàn)長(zhǎng)ombok不會(huì)生成它框全。
Lombok注解代碼:
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);
}
}
@SneakyThrows
@SneakyThrows
可能是具有最多批評(píng)者的項(xiàng)目lombok注解,因?yàn)樗菍?duì)已檢查異常的直接攻擊干签。關(guān)于使用已檢查異常的問(wèn)題存在很多分歧津辩,大量開發(fā)人員認(rèn)為這是一個(gè)失敗的實(shí)驗(yàn)。這些開發(fā)人員會(huì)喜歡@SneakyThrows
。在已檢查/未檢查的異常欄的另一側(cè)的那些開發(fā)人員很可能將此視為隱藏潛在問(wèn)題喘沿。
IllegalAccessException如果IllegalAccessException或某些父類未在throws子句中列出闸度,則 拋出通常會(huì)生成“未處理的異常”錯(cuò)誤:
當(dāng)用@ sneakythrow注解時(shí)蚜印,錯(cuò)誤就消失了.
默認(rèn)情況下莺禁,@SneakyThrows
將允許拋出任何已檢查的異常而不在 throws
子句中聲明。通過(guò)向注解Class<? extends Throwable>
的value
參數(shù)提供一個(gè)throwable classes()數(shù)組窄赋,可以將其限制為一組特定的異常 .
Lombok 注解代碼:
@SneakyThrows
public void testSneakyThrows() {
throw new IllegalAccessException();
}
等價(jià)Java源代碼:
public void testSneakyThrows() {
try {
throw new IllegalAccessException();
} catch (java.lang.Throwable $ex) {
throw lombok.Lombok.sneakyThrow($ex);
}
}
看一下上面的代碼和簽名 Lombok.sneakyThrow(Throwable)
會(huì)讓大多數(shù)人認(rèn)為異常被包裝在一個(gè)RuntimeException
并重新拋出哟冬,但事實(shí)并非如此。該sneakyThrow
方法永遠(yuǎn)不會(huì)正常返回忆绰,而是將提供的throwable
完全保持不變柒傻。
成本和收益
與任何技術(shù)選擇一樣,使用Project Lombok也會(huì)產(chǎn)生正面和負(fù)面影響较木。將Lombok的注解合并到項(xiàng)目中可以大大減少在IDE中生成或手工編寫的樣板代碼行數(shù)。這樣可以減少維護(hù)開銷青柄,減少錯(cuò)誤并提高可讀性伐债。
這并不是說(shuō)在項(xiàng)目中使用Project Lombok注解沒(méi)有缺點(diǎn)。Lombok項(xiàng)目主要旨在填補(bǔ)Java語(yǔ)言的空白致开。因此峰锁,可能會(huì)發(fā)生對(duì)語(yǔ)言的更改,從而妨礙使用Lombok的注解双戳,例如添加第一類屬性支持虹蒋。此外,當(dāng)與基于注解的對(duì)象關(guān)系映射(ORM)框架結(jié)合使用時(shí)飒货,數(shù)據(jù)類上的注解數(shù)量可能開始變得難以處理魄衅。這在很大程度上被Lombok注解取代的代碼量所抵消。但是塘辅,那些避免經(jīng)常使用注解的人可能會(huì)選擇另一種方式晃虫。
缺什么?
Project Lombok提供了delombok用等效源代碼替換Lombok注解的實(shí)用程序】鄱眨可以通過(guò)命令行對(duì)整個(gè)源目錄執(zhí)行此操作
java -jar lombok.jar delombok src -d src-delomboked
或者哲银,提供Ant任務(wù)以結(jié)合到構(gòu)建過(guò)程中。
<target name="delombok">
<taskdef classname="lombok.delombok.ant.DelombokTask"
classpath="WebRoot/WEB-INF/lib/lombok.jar" name="delombok" />
<mkdir dir="src-delomboked" />
<delombok verbose="true" encoding="UTF-8" to="src-delomboked"
from="src" />
</target>
兩者delombok和相應(yīng)的Ant任務(wù)都打包在核心lombok.jar下載中呻惕。除了允許Lombok注解在使用Google Web Toolkit(GWT)或其他不兼容的框架構(gòu)建的應(yīng)用程序中有用之外荆责,delombok在Person類上運(yùn)行 還可以輕松地將使用Lombok注解編寫的類與包含等效樣板內(nèi)聯(lián)的代碼進(jìn)行對(duì)比。
package com.ociweb.jnb.lombok;
import java.util.Date;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NonNull;
@Data
@EqualsAndHashCode(exclude={"address","city","state","zip"})
public class Person {
enum Gender { Male, Female }
@NonNull private String firstName;
@NonNull private String lastName;
@NonNull private final Gender gender;
@NonNull private final Date dateOfBirth;
private String ssn;
private String address;
private String city;
private String state;
private String zip;
}
使用Project Lombok注解的代碼比包含樣板的等效類更簡(jiǎn)潔亚脆。
package com.ociweb.jnb.lombok;
import java.util.Date;
import lombok.NonNull;
public class Person {
enum Gender {
/*public static final*/ Male /* = new Gender() */,
/*public static final*/ Female /* = new Gender() */;
}
@NonNull
private String firstName;
@NonNull
private String lastName;
@NonNull
private final Gender gender;
@NonNull
private final Date dateOfBirth;
private String ssn;
private String address;
private String city;
private String state;
private String zip;
public Person(@NonNull final String firstName, @NonNull final String lastName,
@NonNull final Gender gender, @NonNull final Date dateOfBirth) {
if (firstName == null)
throw new java.lang.NullPointerException("firstName");
if (lastName == null)
throw new java.lang.NullPointerException("lastName");
if (gender == null)
throw new java.lang.NullPointerException("gender");
if (dateOfBirth == null)
throw new java.lang.NullPointerException("dateOfBirth");
this.firstName = firstName;
this.lastName = lastName;
this.gender = gender;
this.dateOfBirth = dateOfBirth;
}
@NonNull
public String getFirstName() {
return firstName;
}
public void setFirstName(@NonNull final String firstName) {
if (firstName == null)
throw new java.lang.NullPointerException("firstName");
this.firstName = firstName;
}
@NonNull
public String getLastName() {
return lastName;
}
public void setLastName(@NonNull final String lastName) {
if (lastName == null)
throw new java.lang.NullPointerException("lastName");
this.lastName = lastName;
}
@NonNull
public Gender getGender() {
return gender;
}
@NonNull
public Date getDateOfBirth() {
return dateOfBirth;
}
public String getSsn() {
return ssn;
}
public void setSsn(final String ssn) {
this.ssn = ssn;
}
public String getAddress() {
return address;
}
public void setAddress(final String address) {
this.address = address
}