Lombok使用指南

Louis碼農工具人發表於2020-08-12

 

一、Lombok 簡介

Lombok 是一款 Java 開發外掛,使得 Java 開發者可以通過其定義的一些註解來消除業務工程中冗長和繁瑣的程式碼,尤其對於簡單的 Java 模型物件(POJO)。在開發環境中使用 Lombok 外掛後,Java 開發人員可以節省出重複構建,諸如 hashCode 和 equals 這樣的方法以及各種業務物件模型的 accessor 和 toString 等方法的大量時間。對於這些方法,Lombok 能夠在編譯原始碼期間自動幫我們生成這些方法,但並不會像反射那樣降低程式的效能。

二、Lombok 安裝

2.1 構建工具

  • Maven

在 Maven 專案的 pom.xml 檔案中新增 Lombok 依賴:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.10</version>
    <scope>provided</scope>
</dependency>
  • Gradle

在 build.gradle 檔案中新增 Lombok 依賴:

dependencies {
    compileOnly 'org.projectlombok:lombok:1.18.10'
    annotationProcessor 'org.projectlombok:lombok:1.18.10'
}
  • Ant

假設在 lib 目錄中已經存在 lombok.jar,然後設定 javac 任務:

<javac srcdir="src" destdir="build" source="1.8">
    <classpath location="lib/lombok.jar" />
</javac>

 

2.2 IDE

由於 Lombok 僅在編譯階段生成程式碼,所以使用 Lombok 註解的原始碼,在 IDE 中會被高亮顯示錯誤,針對這個問題可以通過安裝 IDE 對應的外掛來解決。具體的安裝方式可以參考:https://www.baeldung.com/lombok-ide

Lombok in IntelliJ IDEA

 

Lombok in Eclipse

 

三、Lombok 詳解

註解說明

  • val:用在區域性變數前面,相當於將變數宣告為final
  • @NonNull:給方法引數增加這個註解會自動在方法內對該引數進行是否為空的校驗,如果為空,則丟擲NPE(NullPointerException)
  • @Cleanup:自動管理資源,用在區域性變數之前,在當前變數範圍內即將執行完畢退出之前會自動清理資源,自動生成try-finally這樣的程式碼來關閉流
  • @Getter/@Setter:用在屬性上,再也不用自己手寫setter和getter方法了,還可以指定訪問範圍
  • @ToString:用在類上,可以自動覆寫toString方法,當然還可以加其他引數,例如@ToString(exclude=”id”)排除id屬性,或者@ToString(callSuper=true, includeFieldNames=true)呼叫父類的toString方法,包含所有屬性
  • @EqualsAndHashCode:用在類上,自動生成equals方法和hashCode方法
  • @NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor:用在類上,自動生成無參構造和使用所有引數的建構函式以及把所有@NonNull屬性作為引數的建構函式,如果指定staticName = “of”引數,同時還會生成一個返回類物件的靜態工廠方法,比使用建構函式方便很多
  • @Data:註解在類上,相當於同時使用了@ToString@EqualsAndHashCode@Getter@Setter@RequiredArgsConstrutor這些註解,對於POJO類十分有用
  • @Value:用在類上,是@Data的不可變形式,相當於為屬性新增final宣告,只提供getter方法,而不提供setter方法
  • @Builder:用在類、構造器、方法上,為你提供複雜的builder APIs,讓你可以像如下方式一樣呼叫Person.builder().name("Adam Savage").city("San Francisco").job("Mythbusters").job("Unchained Reaction").build();更多說明參考Builder
  • @SneakyThrows:自動拋受檢異常,而無需顯式在方法上使用throws語句
  • @Synchronized:用在方法上,將方法宣告為同步的,並自動加鎖,而鎖物件是一個私有的屬性$lock$LOCK,而java中的synchronized關鍵字鎖物件是this,鎖在this或者自己的類物件上存在副作用,就是你不能阻止非受控程式碼去鎖this或者類物件,這可能會導致競爭條件或者其它執行緒錯誤
  • @Getter(lazy=true):可以替代經典的Double Check Lock樣板程式碼
  • @Log:根據不同的註解生成不同型別的log物件,但是例項名稱都是log,有六種可選實現類
    • @CommonsLog Creates log = org.apache.commons.logging.LogFactory.getLog(LogExample.class);
    • @Log Creates log = java.util.logging.Logger.getLogger(LogExample.class.getName());
    • @Log4j Creates log = org.apache.log4j.Logger.getLogger(LogExample.class);
    • @Log4j2 Creates log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class);
    • @Slf4j Creates log = org.slf4j.LoggerFactory.getLogger(LogExample.class);
    • @XSlf4j Creates log = org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class);

 

3.1 @Getter and @Setter

你可以使用 @Getter@Setter 註釋任何類或欄位,Lombok 會自動生成預設的 getter/setter 方法。

@Getter

@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface Getter {
  // 若getter方法非public的話,可以設定可訪問級別
    lombok.AccessLevel value() default lombok.AccessLevel.PUBLIC;
    AnyAnnotation[] onMethod() default {};
  // 是否啟用延遲初始化
    boolean lazy() default false;
}

@Setter

@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface Setter {
  // 若setter方法非public的話,可以設定可訪問級別
    lombok.AccessLevel value() default lombok.AccessLevel.PUBLIC;
    AnyAnnotation[] onMethod() default {};
    AnyAnnotation[] onParam() default {};
}

使用示例

@Getter
@Setter
public class GetterAndSetterDemo {
    String firstName;
    String lastName;
    LocalDate dateOfBirth;
}

以上程式碼經過 Lombok 編譯後,會生成如下程式碼:

public class GetterAndSetterDemo {
    String firstName;
    String lastName;
    LocalDate dateOfBirth;

    public GetterAndSetterDemo() {
    }

    // 省略其它setter和getter方法
    public String getFirstName() {
        return this.firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
}

Lazy Getter

@Getter 註解支援一個 lazy 屬性,該屬性預設為 false。當設定為 true 時,會啟用延遲初始化,即當首次呼叫 getter 方法時才進行初始化。

示例

public class LazyGetterDemo {
    public static void main(String[] args) {
        LazyGetterDemo m = new LazyGetterDemo();
        System.out.println("Main instance is created");
        m.getLazy();
    }

    @Getter
    private final String notLazy = createValue("not lazy");

    @Getter(lazy = true)
    private final String lazy = createValue("lazy");

    private String createValue(String name) {
        System.out.println("createValue(" + name + ")");
        return null;
    }
}

以上程式碼經過 Lombok 編譯後,會生成如下程式碼:

public class LazyGetterDemo {
    private final String notLazy = this.createValue("not lazy");
    private final AtomicReference<Object> lazy = new AtomicReference();

    // 已省略部分程式碼
    public String getNotLazy() {
        return this.notLazy;
    }

    public String getLazy() {
        Object value = this.lazy.get();
        if (value == null) {
            synchronized(this.lazy) {
                value = this.lazy.get();
                if (value == null) {
                    String actualValue = this.createValue("lazy");
                    value = actualValue == null ? this.lazy : actualValue;
                    this.lazy.set(value);
                }
            }
        }

        return (String)((String)(value == this.lazy ? null : value));
    }
}

通過以上程式碼可知,呼叫 getLazy 方法時,若發現 value 為 null,則會在同步程式碼塊中執行初始化操作。

3.2 Constructor Annotations

@NoArgsConstructor

使用 @NoArgsConstructor 註解可以為指定類,生成預設的建構函式,@NoArgsConstructor 註解的定義如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface NoArgsConstructor {
  // 若設定該屬性,將會生成一個私有的建構函式且生成一個staticName指定的靜態方法
    String staticName() default "";
    AnyAnnotation[] onConstructor() default {};
  // 設定生成建構函式的訪問級別,預設是public
    AccessLevel access() default lombok.AccessLevel.PUBLIC;
  // 若設定為true,則初始化所有final的欄位為0/null/false
    boolean force() default false;
}

示例

@NoArgsConstructor(staticName = "getInstance")
public class NoArgsConstructorDemo {
    private long id;
    private String name;
    private int age;
}

以上程式碼經過 Lombok 編譯後,會生成如下程式碼:

public class NoArgsConstructorDemo {
    private long id;
    private String name;
    private int age;

    private NoArgsConstructorDemo() {
    }

    public static NoArgsConstructorDemo getInstance() {
        return new NoArgsConstructorDemo();
    }
}

@AllArgsConstructor

使用 @AllArgsConstructor 註解可以為指定類,生成包含所有成員的建構函式,@AllArgsConstructor 註解的定義如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface AllArgsConstructor {
  // 若設定該屬性,將會生成一個私有的建構函式且生成一個staticName指定的靜態方法
    String staticName() default "";
    AnyAnnotation[] onConstructor() default {};
  // 設定生成建構函式的訪問級別,預設是public
    AccessLevel access() default lombok.AccessLevel.PUBLIC;
}

示例

@AllArgsConstructor
public class AllArgsConstructorDemo {
    private long id;
    private String name;
    private int age;
}

以上程式碼經過 Lombok 編譯後,會生成如下程式碼:

public class AllArgsConstructorDemo {
    private long id;
    private String name;
    private int age;

    public AllArgsConstructorDemo(long id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
}

@RequiredArgsConstructor

使用 @RequiredArgsConstructor 註解可以為指定類必需初始化的成員變數,如 final 成員變數,生成對應的建構函式,@RequiredArgsConstructor 註解的定義如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface RequiredArgsConstructor {
  // 若設定該屬性,將會生成一個私有的建構函式且生成一個staticName指定的靜態方法
    String staticName() default "";
    AnyAnnotation[] onConstructor() default {};
  // 設定生成建構函式的訪問級別,預設是public
    AccessLevel access() default lombok.AccessLevel.PUBLIC;
}

示例

@RequiredArgsConstructor
public class RequiredArgsConstructorDemo {
    private final long id;
    private String name;
    private int age;
}

以上程式碼經過 Lombok 編譯後,會生成如下程式碼:

public class RequiredArgsConstructorDemo {
    private final long id;
    private String name;
    private int age;

    public RequiredArgsConstructorDemo(long id) {
        this.id = id;
    }
}

3.3 @EqualsAndHashCode

使用 @EqualsAndHashCode 註解可以為指定類生成 equals 和 hashCode 方法, @EqualsAndHashCode 註解的定義如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface EqualsAndHashCode {
  // 指定在生成的equals和hashCode方法中需要排除的欄位列表
    String[] exclude() default {};
    
  // 顯式列出用於identity的欄位,一般情況下non-static,non-transient欄位會被用於identity
    String[] of() default {};
    
  // 標識在執行欄位計算前,是否呼叫父類的equals和hashCode方法
    boolean callSuper() default false;
    
    boolean doNotUseGetters() default false;
    
    AnyAnnotation[] onParam() default {};
    
    @Deprecated
    @Retention(RetentionPolicy.SOURCE)
    @Target({})
    @interface AnyAnnotation {}
    
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.SOURCE)
    public @interface Exclude {}
    
    @Target({ElementType.FIELD, ElementType.METHOD})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Include {
        String replaces() default "";
    }
}

示例

@EqualsAndHashCode
public class EqualsAndHashCodeDemo {
    String firstName;
    String lastName;
    LocalDate dateOfBirth;
}

以上程式碼經過 Lombok 編譯後,會生成如下程式碼:

public class EqualsAndHashCodeDemo {
    String firstName;
    String lastName;
    LocalDate dateOfBirth;

    public EqualsAndHashCodeDemo() {
    }

    public boolean equals(Object o) {
        if (o == this) {
            return true;
        } else if (!(o instanceof EqualsAndHashCodeDemo)) {
            return false;
        } else {
            EqualsAndHashCodeDemo other = (EqualsAndHashCodeDemo)o;
            if (!other.canEqual(this)) {
                return false;
            } else {
              // 已省略大量程式碼
        }
    }

    public int hashCode() {
        int PRIME = true;
        int result = 1;
        Object $firstName = this.firstName;
        int result = result * 59 + ($firstName == null ? 43 : $firstName.hashCode());
        Object $lastName = this.lastName;
        result = result * 59 + ($lastName == null ? 43 : $lastName.hashCode());
        Object $dateOfBirth = this.dateOfBirth;
        result = result * 59 + ($dateOfBirth == null ? 43 : $dateOfBirth.hashCode());
        return result;
    }
}

3.4 @ToString

使用 @ToString 註解可以為指定類生成 toString 方法, @ToString 註解的定義如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface ToString {
  // 列印輸出時是否包含欄位的名稱
    boolean includeFieldNames() default true;
    
  // 列出列印輸出時,需要排除的欄位列表
    String[] exclude() default {};
    
  // 顯式的列出需要列印輸出的欄位列表
    String[] of() default {};
    
  // 列印輸出的結果中是否包含父類的toString方法的返回結果
    boolean callSuper() default false;
    
    boolean doNotUseGetters() default false;
    
    boolean onlyExplicitlyIncluded() default false;
    
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.SOURCE)
    public @interface Exclude {}
    
    @Target({ElementType.FIELD, ElementType.METHOD})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Include {
        int rank() default 0;
        String name() default "";
    }
}

示例

@ToString(exclude = {"dateOfBirth"})
public class ToStringDemo {
    String firstName;
    String lastName;
    LocalDate dateOfBirth;
}

以上程式碼經過 Lombok 編譯後,會生成如下程式碼:

public class ToStringDemo {
    String firstName;
    String lastName;
    LocalDate dateOfBirth;

    public ToStringDemo() {
    }

    public String toString() {
        return "ToStringDemo(firstName=" + this.firstName + ", lastName=" + this.lastName + ")";
    }
}

3.5 @Data

@Data 註解與同時使用以下的註解的效果是一樣的:

@ToString
@Getter
@Setter
@RequiredArgsConstructor
@EqualsAndHashCode

@Data 註解的定義如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface Data {
    String staticConstructor() default "";
}

示例

@Data
public class DataDemo {
    private Long id;
    private String summary;
    private String description;
}

以上程式碼經過 Lombok 編譯後,會生成如下程式碼:

public class DataDemo {
    private Long id;
    private String summary;
    private String description;

    public DataDemo() {
    }

    // 省略summary和description成員屬性的setter和getter方法
    public Long getId() {
        return this.id;
    }
  
    public void setId(Long id) {
        this.id = id;
    }

    public boolean equals(Object o) {
        if (o == this) {
            return true;
        } else if (!(o instanceof DataDemo)) {
            return false;
        } else {
            DataDemo other = (DataDemo)o;
            if (!other.canEqual(this)) {
                return false;
            } else {
               // 已省略大量程式碼
            }
        }
    }

    protected boolean canEqual(Object other) {
        return other instanceof DataDemo;
    }

    public int hashCode() {
        int PRIME = true;
        int result = 1;
        Object $id = this.getId();
        int result = result * 59 + ($id == null ? 43 : $id.hashCode());
        Object $summary = this.getSummary();
        result = result * 59 + ($summary == null ? 43 : $summary.hashCode());
        Object $description = this.getDescription();
        result = result * 59 + ($description == null ? 43 : $description.hashCode());
        return result;
    }

    public String toString() {
        return "DataDemo(id=" + this.getId() + ", summary=" + this.getSummary() + ", description=" + this.getDescription() + ")";
    }
}

3.6 @Log

若你將 @Log 的變體放在類上(適用於你所使用的日誌記錄系統的任何一種);之後,你將擁有一個靜態的 final log 欄位,然後你就可以使用該欄位來輸出日誌。

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

3.7 @Synchronized

@Synchronized 是同步方法修飾符的更安全的變體。與 synchronized 一樣,該註解只能應用在靜態和例項方法上。它的操作類似於 synchronized 關鍵字,但是它鎖定在不同的物件上。synchronized 關鍵字應用在例項方法時,鎖定的是 this 物件,而應用在靜態方法上鎖定的是類物件。對於 @Synchronized 註解宣告的方法來說,它鎖定的是 

或lock。@Synchronized 註解的定義如下:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Synchronized {
  // 指定鎖定的欄位名稱
    String value() default "";
}

示例

public class SynchronizedDemo {
    private final Object readLock = new Object();

    @Synchronized
    public static void hello() {
        System.out.println("world");
    }

    @Synchronized
    public int answerToLife() {
        return 42;
    }

    @Synchronized("readLock")
    public void foo() {
        System.out.println("bar");
    }
}

以上程式碼經過 Lombok 編譯後,會生成如下程式碼:

public class SynchronizedDemo {
    private static final Object $LOCK = new Object[0];
    private final Object $lock = new Object[0];
    private final Object readLock = new Object();

    public SynchronizedDemo() {
    }

    public static void hello() {
        synchronized($LOCK) {
            System.out.println("world");
        }
    }

    public int answerToLife() {
        synchronized(this.$lock) {
            return 42;
        }
    }

    public void foo() {
        synchronized(this.readLock) {
            System.out.println("bar");
        }
    }
}

3.8 @Builder

使用 @Builder 註解可以為指定類實現建造者模式,該註解可以放在類、建構函式或方法上。@Builder 註解的定義如下:

@Target({TYPE, METHOD, CONSTRUCTOR})
@Retention(SOURCE)
public @interface Builder {
    @Target(FIELD)
    @Retention(SOURCE)
    public @interface Default {}

  // 建立新的builder例項的方法名稱
    String builderMethodName() default "builder";
    // 建立Builder註解類對應例項的方法名稱
    String buildMethodName() default "build";
    // builder類的名稱
    String builderClassName() default "";
    
    boolean toBuilder() default false;
    
    AccessLevel access() default lombok.AccessLevel.PUBLIC;
    
    @Target({FIELD, PARAMETER})
    @Retention(SOURCE)
    public @interface ObtainVia {
        String field() default "";
        String method() default "";
        boolean isStatic() default false;
    }
}

示例

@Builder
public class BuilderDemo {
    private final String firstname;
    private final String lastname;
    private final String email;
}

以上程式碼經過 Lombok 編譯後,會生成如下程式碼:

public class BuilderDemo {
    private final String firstname;
    private final String lastname;
    private final String email;

    BuilderDemo(String firstname, String lastname, String email) {
        this.firstname = firstname;
        this.lastname = lastname;
        this.email = email;
    }

    public static BuilderDemo.BuilderDemoBuilder builder() {
        return new BuilderDemo.BuilderDemoBuilder();
    }

    public static class BuilderDemoBuilder {
        private String firstname;
        private String lastname;
        private String email;

        BuilderDemoBuilder() {
        }

        public BuilderDemo.BuilderDemoBuilder firstname(String firstname) {
            this.firstname = firstname;
            return this;
        }

        public BuilderDemo.BuilderDemoBuilder lastname(String lastname) {
            this.lastname = lastname;
            return this;
        }

        public BuilderDemo.BuilderDemoBuilder email(String email) {
            this.email = email;
            return this;
        }

        public BuilderDemo build() {
            return new BuilderDemo(this.firstname, this.lastname, this.email);
        }

        public String toString() {
            return "BuilderDemo.BuilderDemoBuilder(firstname=" + this.firstname + ", lastname=" + this.lastname + ", email=" + this.email + ")";
        }
    }
}

3.9 @SneakyThrows

@SneakyThrows 註解用於自動丟擲已檢查的異常,而無需在方法中使用 throw 語句顯式丟擲。@SneakyThrows 註解的定義如下:

@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.SOURCE)
public @interface SneakyThrows {
    // 設定你希望向上拋的異常類
    Class<? extends Throwable>[] value() default java.lang.Throwable.class;
}

示例

public class SneakyThrowsDemo {
    @SneakyThrows
    @Override
    protected Object clone() {
        return super.clone();
    }
}

以上程式碼經過 Lombok 編譯後,會生成如下程式碼:

public class SneakyThrowsDemo {
    public SneakyThrowsDemo() {
    }

    protected Object clone() {
        try {
            return super.clone();
        } catch (Throwable var2) {
            throw var2;
        }
    }
}

3.10 @NonNull

你可以在方法或建構函式的引數上使用 @NonNull 註解,它將會為你自動生成非空校驗語句。@NonNull 註解的定義如下:

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE, ElementType.TYPE_USE})
@Retention(RetentionPolicy.CLASS)
@Documented
public @interface NonNull {
}

示例

public class NonNullDemo {
    @Getter
    @Setter
    @NonNull
    private String name;
}

以上程式碼經過 Lombok 編譯後,會生成如下程式碼:

public class NonNullDemo {
    @NonNull
    private String name;

    public NonNullDemo() {
    }

    @NonNull
    public String getName() {
        return this.name;
    }

    public void setName(@NonNull String name) {
        if (name == null) {
            throw new NullPointerException("name is marked non-null but is null");
        } else {
            this.name = name;
        }
    }
}

3.11 @Clean

@Clean 註解用於自動管理資源,用在區域性變數之前,在當前變數範圍內即將執行完畢退出之前會自動清理資源,自動生成 try-finally 這樣的程式碼來關閉流。

@Target(ElementType.LOCAL_VARIABLE)
@Retention(RetentionPolicy.SOURCE)
public @interface Cleanup {
  // 設定用於執行資源清理/回收的方法名稱,對應方法不能包含任何引數,預設名稱為close。
    String value() default "close";
}

示例

public class CleanupDemo {
    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 編譯後,會生成如下程式碼:

public class CleanupDemo {
    public CleanupDemo() {
    }

    public static void main(String[] args) throws IOException {
        FileInputStream in = new FileInputStream(args[0]);

        try {
            FileOutputStream out = new FileOutputStream(args[1]);

            try {
                byte[] b = new byte[10000];

                while(true) {
                    int r = in.read(b);
                    if (r == -1) {
                        return;
                    }

                    out.write(b, 0, r);
                }
            } finally {
                if (Collections.singletonList(out).get(0) != null) {
                    out.close();
                }

            }
        } finally {
            if (Collections.singletonList(in).get(0) != null) {
                in.close();
            }
        }
    }
}

3.11 @With

在類的欄位上應用 @With 註解之後,將會自動生成一個 withFieldName(newValue) 的方法,該方法會基於 newValue 呼叫相應建構函式,建立一個當前類對應的例項。@With 註解的定義如下:

@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface With {
    AccessLevel value() default AccessLevel.PUBLIC;

    With.AnyAnnotation[] onMethod() default {};

    With.AnyAnnotation[] onParam() default {};

    @Deprecated
    @Retention(RetentionPolicy.SOURCE)
    @Target({})
    public @interface AnyAnnotation {
    }
}

示例

public class WithDemo {
    @With(AccessLevel.PROTECTED)
    @NonNull
    private final String name;
    @With
    private final int age;

    public WithDemo(String name, int age) {
        if (name == null) throw new NullPointerException();
        this.name = name;
        this.age = age;
    }
}

以上程式碼經過 Lombok 編譯後,會生成如下程式碼:

public class WithDemo {
    @NonNull
    private final String name;
    private final int age;

    public WithDemo(String name, int age) {
        if (name == null) {
            throw new NullPointerException();
        } else {
            this.name = name;
            this.age = age;
        }
    }

    protected WithDemo withName(@NonNull String name) {
        if (name == null) {
            throw new NullPointerException("name is marked non-null but is null");
        } else {
            return this.name == name ? this : new WithDemo(name, this.age);
        }
    }

    public WithDemo withAge(int age) {
        return this.age == age ? this : new WithDemo(this.name, age);
    }
}

3.12 其它特性

val

val 用在區域性變數前面,相當於將變數宣告為 final,此外 Lombok 在編譯時還會自動進行型別推斷。val 的使用示例:

public class ValExample {
  public String example() {
    val example = new ArrayList<String>();
    example.add("Hello, World!");
    val foo = example.get(0);
    return foo.toLowerCase();
  }
  
  public void example2() {
    val map = new HashMap<Integer, String>();
    map.put(0, "zero");
    map.put(5, "five");
    for (val entry : map.entrySet()) {
      System.out.printf("%d: %s\n", entry.getKey(), entry.getValue());
    }
  }
}

以上程式碼等價於:

public class ValExample {
  public String example() {
    final ArrayList<String> example = new ArrayList<String>();
    example.add("Hello, World!");
    final String foo = example.get(0);
    return foo.toLowerCase();
  }
  
  public void example2() {
    final HashMap<Integer, String> map = new HashMap<Integer, String>();
    map.put(0, "zero");
    map.put(5, "five");
    for (final Map.Entry<Integer, String> entry : map.entrySet()) {
      System.out.printf("%d: %s\n", entry.getKey(), entry.getValue());
    }
  }
}

 

四、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,增加新的節點(所謂程式碼),最終通過分析和生成位元組碼

自從Java 6起,javac就支援“JSR 269 Pluggable Annotation Processing API”規範,只要程式實現了該API,就能在javac執行的時候得到呼叫

  1. 常用的專案管理工具Maven所使用的java編譯工具來源於配置的第三方工具,如果我們配置這個第三方工具為Oracle javac的話,那麼Maven也就直接支援lombok了;
  2. Intellij Idea配置的編譯工具為Oracle javac的話,也就直接支援lombok了。

IDE工具問題解決:

現在有一個A類,其中有一些欄位,沒有建立它們的setter和getter方法,使用了lombok的@Data註解,另外有一個B類,它呼叫了A類例項的相應欄位的setter和getter方法

編譯A類和B類所在的專案,並不會報錯,因為最終生成的A類位元組碼檔案中存在相應欄位的setter和getter方法

但是,IDE發現B類原始碼中所使用的A類例項的setter和getter方法在A類原始碼中找不到定義,IDE會認為這是錯誤

要解決以上這個不是真正錯誤的錯誤,可以下載安裝Intellij Idea中的"Lombok plugin"。

 

 

本文參考連結:segmentfaul【阿寶哥】https://segmentfault.com/a/1190000020864572

本文參考連結:微信公眾號【江南一點雨】https://mp.weixin.qq.com/s/-4W5-fOK0sGSaNBktXA-YQ

本文參考連結:掘金【猿碼道】https://juejin.im/post/6844903557016076302

 

相關文章