lombok深度指南

Contents

介紹

Project Lombok是一個java庫,它可以自動插入你的編輯器并構建工具,增強你的java代碼。
永遠不要再寫另一個getter或equals方法,使用一個注解,您的類具有一個功能齊全的構建器,自動化您的日志記錄變量等等:

Image illustrating the availability of new methods on a data class in Eclipse.

安裝

idea 安裝lombok

Jetbrains的IntelliJ IDEA的編輯與lombok兼容。

添加Lombok IntelliJ插件以添加對IntelliJ的lombok支持:

  • File > Settings > Plugins
  • 點擊 Browse repositories...
  • 搜索 Lombok Plugin
  • 點擊 Install plugin
  • 重啟IntelliJ IDEA

Lombok 注解

典型的Java項目將數百行代碼用于定義簡單數據類所需的樣板文件并不罕見。這些類通常包含這些字段的若干字段,getter和setter,以及equals和 hashCode實施方式。在最簡單的場景中,Lombok項目可以將這些類減少到必需的字段和單個@Data注解。

當然,最簡單的場景不一定是開發人員日常所面臨的場景。出于這個原因,Project Lombok中有許多注解允許對類的結構和行為進行更細粒度的控制。

@Getter and @Setter

@Getter@Setter注解分別為字段生成getter和setter。生成的getter方法正確地遵循了布爾屬性的約定,從而為任何布爾字段foo生成一個isFoo getter方法名,而不是getFoo

需要注意的是,如果帶注解字段所屬的類包含與要生成的getter或setter同名的方法,無論參數或返回類型如何,都不會生成相應的方法。

@Getter@Setter注解都采用一個可選參數來指定生成方法的訪問級別。

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注釋用于指示對對應成員進行快速失敗null檢查的需要。當放置在Lombok正在為其生成setter方法的字段上時,將生成null檢查,如果提供了null值,將導致NullPointerException。此外,如果Lombok正在為擁有的類生成構造函數,那么字段將被添加到構造函數簽名中,null檢查將包含在生成的構造函數代碼中。

IntelliJ IDEA和FindBugs等中的 注解鏡像@NotNull@NonNull注解。對于主題的這些變化,Lombok是注解不可知的。如果Lombok遇到任何使用該名稱的注解注解的成員,@NotNull或者@NonNull它將通過生成適當的相應代碼來遵守它。Project Lombok的作者進一步評論說,如果將此類型的注解添加到Java,則Lombok版本將被刪除。

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

@ToString

此注釋生成toString方法的實現。默認情況下,任何非靜態字段都將以名稱-值對的形式包含在方法的輸出中。如果需要,可以通過將注釋參數includeFieldNames設置為false來排除在輸出中包含屬性名。

通過在exclude 參數中包含其字段名稱,可以從生成的方法的輸出中排除特定字段。或者,該of參數可用于僅列出輸出中所需的那些字段。toString通過將callSuper參數 設置為,還可以包括超類方法的輸出true

Lombok 注解代碼:

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

@EqualsAndHashCode

這個類級注解將導致Lombok生成兩者 equalshashCode方法,因為這兩者是由hashCode契約本質上綁定在一起的。默認情況下,兩個方法都將考慮類中非靜態或瞬態的任何字段。很像@ToStringexclude提供參數是為了防止字段包含在生成的邏輯中。也可以使用該 of參數僅列出應考慮的那些字段。

同樣@ToStringcallSuper 這個注解有一個參數。將其設置為true將導致 在考慮當前類中的字段之前equals通過調用equals超類來驗證相等性。對于該hashCode方法,它導致在hashCode計算散列時結合超類的結果。設置callSuper為true時,請注意確保父類中的equals方法正確處理實例類型檢查。如果父類檢查該類是否屬于特定類型而不僅僅是兩個對象的類相同,則可能導致不希望的結果。如果超類使用生成的Lombok equals方法,這不是問題。但是,其他實現可能無法正確處理此情況。另請注意,callSuper當類僅擴展時Object,無法設置 為true ,因為這會導致實例相等性檢查使字段比較短路。這是由于生成的方法調用了equals實現 Object,如果被比較的兩個實例不是同一個實例,則返回false。因此,在這種情況下,Lombok將生成編譯時錯誤。

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

@Data

@Data注解很可能在項目lombok工具箱中最常用的注解。它結合的功能@ToString@EqualsAndHashCode@Getter@Setter。本質上,使用 @Data的一類是一樣的帶有默認注解類@ToString@EqualsAndHashCode 以及與兩個注解每個字段@Getter 和@Setter。對類進行注解@Data 也會觸發Lombok的構造函數生成。這會添加一個公共構造函數,它將任何@NonNullfinal 字段作為參數。這提供了普通舊Java對象(PO??JO)所需的一切。

雖然@Data非常有用,但它不提供與其他Lombok注解相同的控制粒度。要覆蓋默認方法生成行為,請使用其他Lombok注解之一注解類,字段或方法,并指定必要的參數值以實現所需的效果。

@Data確實提供了一個可用于生成靜態工廠方法的參數選項。將staticConstructor參數的值設置為 所需的方法名稱將導致Lombok將生成的構造函數設置為private,并公開給定名稱的靜態工廠方法。

Lombok 注解代碼:

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

@Cleanup

@Cleanup注解可以用來保證分配的資源被釋放。當使用帶注解的局部變量時@Cleanup,任何后續代碼都包含在一個 try/finally塊中,該塊保證在當前作用域的末尾調用cleanup方法。默認情況下,@Cleanup 假設清理方法命名為“close”,與輸入和輸出流一樣。但是,可以為注解的value參數提供不同的方法名稱。只有不帶參數的清理方法才能與此注解一起使用。

使用@Cleanup注解時還需要注意一點。如果清理方法拋出異常,它將搶占方法體中引發的任何異常。這可能導致問題的實際原因被掩蓋,并且在選擇使用Project Lombok的資源管理時應該考慮到這一點。此外,隨著Java 7中的自動資源管理,這個特定的注解可能相對短暫。

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

等價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

在一個方法上使用synchronized關鍵字可能會導致不好的結果,任何一個開發多線程軟件的開發人員都可以證明這一點。synchronized關鍵字在實例方法或靜態方法的類對象中會鎖定當前對象。這意味著開發人員控制之外的代碼有可能鎖定同一個對象,從而導致死鎖。通常建議在單獨的對象上顯式鎖定,該對象專門用于此目的,而不是以允許非請求鎖定的方式公開。Project Lombok正是為此目的提供了@Synchronized注解。

@Synchronized注解一個實例方法將提示Lombok生成一個名為lock的私有鎖定字段,該方法將在執行之前鎖定該字段。類似地,以相同的方式注解靜態方法將為靜態方法生成一個名為`LOCK`的私有靜態對象,以便以相同的方式使用。可以通過為注解的值參數提供字段名來指定不同的鎖定對象。當提供字段名時,開發人員必須定義該屬性,因為Lombok不會生成它。

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可能是具有最多批評者的項目lombok注解,因為它是對已檢查異常的直接攻擊。關于使用已檢查異常的問題存在很多分歧,大量開發人員認為這是一個失敗的實驗。這些開發人員會喜歡@SneakyThrows。在已檢查/未檢查的異常欄的另一側的那些開發人員很可能將此視為隱藏潛在問題。

IllegalAccessException如果IllegalAccessException或某些父類未在throws子句中列出,則 拋出通常會生成“未處理的異常”錯誤:

Screenshot of Eclipse generating an error message regarding unhandled exceptions.

當用@ sneakythrow注解時,錯誤就消失了.

Screenshot of a method annotated with @SneakyThrows and generating no error in Eclipse.

默認情況下,@SneakyThrows將允許拋出任何已檢查的異常而不在 throws子句中聲明。通過向注解Class<? extends Throwable>value參數提供一個throwable classes()數組,可以將其限制為一組特定的異常 .

Lombok 注解代碼:

@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)會讓大多數人認為異常被包裝在一個RuntimeException 并重新拋出,但事實并非如此。該sneakyThrow 方法永遠不會正常返回,而是將提供的throwable完全保持不變。

成本和收益

與任何技術選擇一樣,使用Project Lombok也會產生正面和負面影響。將Lombok的注解合并到項目中可以大大減少在IDE中生成或手工編寫的樣板代碼行數。這樣可以減少維護開銷,減少錯誤并提高可讀性。

這并不是說在項目中使用Project Lombok注解沒有缺點。Lombok項目主要旨在填補Java語言的空白。因此,可能會發生對語言的更改,從而妨礙使用Lombok的注解,例如添加第一類屬性支持。此外,當與基于注解的對象關系映射(ORM)框架結合使用時,數據類上的注解數量可能開始變得難以處理。這在很大程度上被Lombok注解取代的代碼量所抵消。但是,那些避免經常使用注解的人可能會選擇另一種方式。

缺什么?

Project Lombok提供了delombok用等效源代碼替換Lombok注解的實用程序。可以通過命令行對整個源目錄執行此操作

java -jar lombok.jar delombok src -d src-delomboked

或者,提供Ant任務以結合到構建過程中。

<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和相應的Ant任務都打包在核心lombok.jar下載中。除了允許Lombok注解在使用Google Web Toolkit(GWT)或其他不兼容的框架構建的應用程序中有用之外,delombok在Person類上運行 還可以輕松地將使用Lombok注解編寫的類與包含等效樣板內聯的代碼進行對比。

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注解的代碼比包含樣板的等效類更簡潔。

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
   }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。