lombok

相識

lombok想必已經有很多人已經使用了很長時間了,而我卻是第一次接觸到,有點呆。lombok主要是用於減少重複代碼,通過一組簡單的註釋取代一些重複的Java代碼。對於lombok的評價褒貶不一,有的人覺得特別方便,有的人覺得改變了一成不變的代碼結構,增加了代碼維護成本(有的人沒有用過lombok),我是覺得每一個工具誕生肯定是有他誕生的價值的,多學一個是一個啊,小老弟,用不用再說。:)

官方文檔地址

官方API地址

官方註解介紹地址

準備

1、下載idea插件

我這裏已經安裝好了,沒有安裝的時候按鈕應該是Install

这里写图片描述

2、pom文件引入project lombok的maven依賴

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

lombok maven版本

註解介紹

@Getter和@Setter

顧名思義,生成get和set方法的註解,@Getter和@Setter發生在編譯階段,編譯之後,@Getter和@Setter相關的註解就消失了,取而代之的是相應的get和set方法。

public class LombokTest {
    @Getter @Setter
    private boolean flag;
}

lombok遵循了boolean類型的get方法的約定,我們來看一下編譯後的代碼(等效Java代碼)吧。

public class LombokTest {
    private boolean flag;

    public LombokTest() {
    }

    public boolean isFlag() {
        return this.flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}

可以看到boolean類型的get方法是isFlag()而不是getFlag()。我們還可定義生成get和set方法的訪問級別。

public class LombokTest {
    @Getter(AccessLevel.PUBLIC)
    @Setter(AccessLevel.PROTECTED)
    private String name;
}

編譯後的代碼(等效Java代碼)

public class LombokTest {
    private String name;

    public LombokTest() {
    }

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

    protected void setName(String name) {
        this.name = name;
    }
}

可以看到setName的訪問級別是protected。lombok提供了下面幾種級別。

这里写图片描述

AccessLevel枚舉類的源代碼。

public enum AccessLevel {
    PUBLIC, // public
    MODULE, // 編譯後相當於 default
    PROTECTED, // protected
    PACKAGE, // default
    PRIVATE, // private
    NONE; // 不生成

    private AccessLevel() {
    }
}

最後@Getter和@Setter註解是可以寫在類級別的,作用於所有的成員變量,無法細粒度的控制訪問級別。

@Getter(AccessLevel.MODULE)
@Setter(AccessLevel.PROTECTED)
public class LombokTest {
    private String name;
    private boolean flag;
}

編譯後的代碼(等效Java代碼)

public class LombokTest {
    private String name;
    private boolean flag;

    public LombokTest() {
    }

    String getName() {
        return this.name;
    }

    boolean isFlag() {
        return this.flag;
    }

    protected void setName(String name) {
        this.name = name;
    }

    protected void setFlag(boolean flag) {
        this.flag = flag;
    }
}

@NonNull

變量空檢查,如果@NonNull註解使用在構造函數的字段時,構造函數也會進行空變量檢查。不可用於類級別。

public class LombokTest {
    @NonNull
    @Setter
    @Getter
    private String name;
    
    public LombokTest(@NonNull String name) {
        this.name = name;
    }
}

編譯後的代碼(等效Java代碼)

public class LombokTest {
    @NonNull
    private String name;

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

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

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

@ToString

用於生成toString()方法。只能用於類級別。static修飾的變量是不能生成在toString()方法中的,原因是static修飾的變量在類創建的時候就生成了,只有一個;而普通的變量屬於對象,每創建一個新的對象都會有一個。可以理解爲static修飾的變量屬於類,而普通變量數據對象,這樣可能好理解一點。

@ToString
public class LombokTest {
    private String name;
    private static String phone;
}

編譯後的代碼(等效Java代碼)

public class LombokTest {
    private String name;
    private static String phone;

    public LombokTest() {
    }

    public String toString() {
        return "YmlProperties(name=" + this.name + ")";
    }
}

默認情況下出了static修飾的字段都將以name-value的形式生成在toString()方法中。同時toString註解中還可以設置一些屬性來細粒度的控制toString()方法。

下面是ToString註解的源碼和解釋:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface ToString {
    boolean includeFieldNames() default true; // 生成toString()方法時默認爲 name-value的形式,設置爲false時 則 value的形式。

    String[] exclude() default {}; // 不包含指定字段

    String[] of() default {}; // 包含指定字段

    boolean callSuper() default false; // 是否調用父類的toString()方法

    boolean doNotUseGetters() default false; // 生成toString()方法是否直接訪問字段,設置爲true時直接訪問字段(如:this.name),false時通過getter()方法進行訪問(如:this.getName())。當然如果沒有生成getter()方法,無論是否設置都直接訪問字段

    boolean onlyExplicitlyIncluded() default false; // 是否僅僅包含添加了@ToString.Include的字段,默認爲false。

    @Target({ElementType.FIELD})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Exclude { // 使用@ToString.Exclude修飾字段,需要和@ToString註解一起使用,單獨使用無效。
    }

    @Target({ElementType.FIELD, ElementType.METHOD})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Include {// 使用@ToString.Include修飾字段,需要和@ToString註解一起使用,單獨使用無效。
        int rank() default 0;

        String name() default "";
    }
}

注意:

  • of和exclude屬性設置多個字段時,以字符串數組的形式,如:
@ToString(of = {"name","phone"}) // 包含name和phone字段
@ToString(exclude = {"name","phone"}) // 不包含name和phone字段
  • 使用@ToString.Include或者@ToString.Exclude註解時也需要使用@ToString註解,否者無效。
@ToString
public class LombokTest {
    @ToString.Include
    private String name;
    @ToString.Exclude
    private String phone;
}

@EqualsAndHashCode

生成equals()和hashCode()方法。默認使用所有的非static和非transient字段生成這兩個方法。

@EqualsAndHashCode
public class LombokTest {
    private String name;
    private String phone;
    private boolean flag;
}

編譯後的代碼(等效Java代碼)

public class LombokTest {
    private String name;
    private String phone;
    private boolean flag;

    public LombokTest() {
    }

    public boolean equals(Object o) {
        if (o == this) {
            return true;
        } else if (!(o instanceof LombokTest)) {
            return false;
        } else {
            LombokTest other = (LombokTest)o;
            if (!other.canEqual(this)) {
                return false;
            } else {
                label39: {
                    Object this$name = this.name;
                    Object other$name = other.name;
                    if (this$name == null) {
                        if (other$name == null) {
                            break label39;
                        }
                    } else if (this$name.equals(other$name)) {
                        break label39;
                    }

                    return false;
                }

                Object this$phone = this.phone;
                Object other$phone = other.phone;
                if (this$phone == null) {
                    if (other$phone != null) {
                        return false;
                    }
                } else if (!this$phone.equals(other$phone)) {
                    return false;
                }

                if (this.flag != other.flag) {
                    return false;
                } else {
                    return true;
                }
            }
        }
    }

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

    public int hashCode() {
        int PRIME = true;
        int result = 1;
        Object $name = this.name;
        int result = result * 59 + ($name == null ? 43 : $name.hashCode());
        Object $phone = this.phone;
        result = result * 59 + ($phone == null ? 43 : $phone.hashCode());
        result = result * 59 + (this.flag ? 79 : 97);
        return result;
    }
}

和ToString註解相似也可以通過設置一些屬性來控制決堤生成的hashCode()和equals()方法。下面是@EqualsAndHashCode註解的源碼和解釋,和@ToString一樣的屬性就沒有寫註釋了。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface EqualsAndHashCode {
    String[] exclude() default {};

    String[] of() default {};

    boolean callSuper() default false;// 是否使用父類的equals和toString方法,注意:父類爲Object時無法設置爲true。

    boolean doNotUseGetters() default false;

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

    boolean onlyExplicitlyIncluded() default false;

    /** @deprecated */
    @Deprecated
    @Retention(RetentionPolicy.SOURCE)
    @Target({})
    public @interface AnyAnnotation { // 過時的方法(deprecated)
    }

    @Target({ElementType.FIELD})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Exclude {
    }

    @Target({ElementType.FIELD, ElementType.METHOD})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Include {
        String replaces() default "";
    }
}

@Data

這是lombok中使用的最多的註解,相當於@ToString@EqualsAndHashCode@RequiredArgsConstructor@Getter@Setter五個註解的功能。雖然使用起來方法,但是失去了細粒度的控制。如果需要細粒度控制,則需要進行註解的覆蓋。

@Data
public class LombokTest {
    private String name;
    private String phone;
    private boolean flag;
}

編譯後的代碼(等效Java代碼)

public class LombokTest {
    private String name;
    private String phone;
    private boolean flag;

    public LombokTest() {
    }

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

    public String getPhone() {
        return this.phone;
    }

    public boolean isFlag() {
        return this.flag;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public boolean equals(Object o) {
        if (o == this) {
            return true;
        } else if (!(o instanceof LombokTest)) {
            return false;
        } else {
            LombokTest other = (LombokTest)o;
            if (!other.canEqual(this)) {
                return false;
            } else {
                label39: {
                    Object this$name = this.getName();
                    Object other$name = other.getName();
                    if (this$name == null) {
                        if (other$name == null) {
                            break label39;
                        }
                    } else if (this$name.equals(other$name)) {
                        break label39;
                    }

                    return false;
                }

                Object this$phone = this.getPhone();
                Object other$phone = other.getPhone();
                if (this$phone == null) {
                    if (other$phone != null) {
                        return false;
                    }
                } else if (!this$phone.equals(other$phone)) {
                    return false;
                }

                if (this.isFlag() != other.isFlag()) {
                    return false;
                } else {
                    return true;
                }
            }
        }
    }

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

    public int hashCode() {
        int PRIME = true;
        int result = 1;
        Object $name = this.getName();
        int result = result * 59 + ($name == null ? 43 : $name.hashCode());
        Object $phone = this.getPhone();
        result = result * 59 + ($phone == null ? 43 : $phone.hashCode());
        result = result * 59 + (this.isFlag() ? 79 : 97);
        return result;
    }

    public String toString() {
        return "LombokTest(name=" + this.getName() + ", phone=" + this.getPhone() + ", flag=" + this.isFlag() + ")";
    }
}

下面看一下@Data註解的源碼。

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

可以看到提供了一個,staticConstructor(),這個方法的作用是私有化構造器,並生成一個靜態的獲取實例的方法,這樣就可通過類名來獲取實例(如:LombokTest.getLombokTest())

@Data(staticConstructor = "getLombokTest")
private LombokTest() {
}

public static LombokTest getLombokTest() {
    return new LombokTest();
}
// ...省略其他代碼

@Cleanup

自動釋放資源,主要用於IO流的操作,不需要手動編寫釋放資源的try/finally代碼塊。

public class LombokTest {
    public static void main(String[] args) throws Exception {
        @Cleanup InputStream is = new FileInputStream(args[0]);
        @Cleanup OutputStream os = new FileOutputStream(args[0]);
    }
}

編譯後的代碼(等效Java代碼)

public class LombokTest {
    public static void main(String[] args) throws Exception {
        FileInputStream is = new FileInputStream(args[0]);

        try {
            OutputStream os = new FileOutputStream(args[0]);
            if (Collections.singletonList(os).get(0) != null) {
                os.close();
            }
        } finally {
            if (Collections.singletonList(is).get(0) != null) {
                is.close();
            }
        }
    }
}

還是挺方便的。再來看看@Cleanup註解的源代碼

@Target({ElementType.LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface Cleanup {
    String value() default "close";
}

可以看到默認調用的是close方法,也就是說我們可以設置finally方法體具體調用的流處理方法。如:

public static void main(String[] args) throws Exception {
    @Cleanup(value = "available") InputStream is = new FileInputStream(args[0]);
    @Cleanup OutputStream os = new FileOutputStream(args[0]);
}

編譯後的代碼(等效Java代碼)

这里写图片描述

具體可以指定哪些字段,需要流對象支持哪些方法了。如InputStream:

这里写图片描述

@Synchronized

同步代碼塊,只能使用在類方法(static)或者對象方法(普通方法)上,synchronized關鍵字鎖住的是this,@Synchronized註解默認使用的鎖爲$lock,靜態的鎖爲$LOCK,也可以自己指定鎖對象,如示例中的:world

public class LombokTest {
    @Synchronized
    public void hello() {

    }

    @Synchronized
    public static void helloWorld() {

    }

    private final Object world = new Object();
    
    @Synchronized("world")
    public void world() {

    }
}

編譯後的代碼(等效Java代碼)

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

    public LombokTest() {
    }

    public void hello() {
        Object var1 = this.$lock;
        synchronized(this.$lock) {
            ;
        }
    }

    public static void helloWorld() {
        Object var0 = $LOCK;
        synchronized($LOCK) {
            ;
        }
    }

    public void world() {
        Object var1 = this.world;
        synchronized(this.world) {
            ;
        }
    }
}

@SneakyThrows

個人覺得沒有啥意義,唯一的作用可能就是不用手動捕獲異常或者向上拋出,而是通過lombok幫你捕獲。。。可以指定捕獲的異常類型,默認捕獲的是Throwable。

public class LombokTest {
    @SneakyThrows()
    public void testSneakyThrows() {
       throw new IllegalAccessException();
    }

    @SneakyThrows(IllegalAccessException.class)
    public void testSneakyThrows2() {
        throw new IllegalAccessException();
    }
}

編譯後的代碼(等效Java代碼)

public class LombokTest {
    public LombokTest() {
    }

    public void testSneakyThrows() {
        try {
            throw new IllegalAccessException();
        } catch (Throwable var2) {
            throw var2;
        }
    }

    public void testSneakyThrows2() {
        try {
            throw new IllegalAccessException();
        } catch (IllegalAccessException var2) {
            throw var2;
        }
    }
}

感覺有點呆。。。官方建議在沒有深思熟慮之前不要使用這個註解。。。下面是官網上的一句話:

  • You can pass any number of exceptions to the @SneakyThrows annotation. If you pass no exceptions, you may throw any exception sneakily.

@NoArgsConstructor,@ RequiredArgsConstructor,@ AllArgsConstructor

@NoArgsConstructor 生成一個無參構造器。

@NoArgsConstructor註解源代碼,另外兩個註解的源碼和這個很類似,就不貼出來了。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface NoArgsConstructor {
    String staticName() default ""; // 如果指定了相應字段,則私有化構造並生成一個靜態的獲取實例方法。

    NoArgsConstructor.AnyAnnotation[] onConstructor() default {};

    AccessLevel access() default AccessLevel.PUBLIC;

    boolean force() default false;

    /** @deprecated */
    @Deprecated
    @Retention(RetentionPolicy.SOURCE)
    @Target({})
    public @interface AnyAnnotation { // 過時的(deprecated)
    }
}

@ RequiredArgsConstructor 生成包含必須處理的字段的構造器,如:final修飾(必須初始化)、@NonNull註解修飾的。

@RequiredArgsConstructor
public class LombokTest {
    private String name;
    private String phone;
    private transient boolean flag;
    private final String finalFiled;
    private static int num;
}

編譯後的代碼(等效Java代碼)

public class LombokTest {
    private String name;
    private String phone;
    private transient boolean flag;
    private final String finalFiled;
    private static int num;

    public LombokTest(String finalFiled) {
        this.finalFiled = finalFiled;
    }
}

@ AllArgsConstructor 生成一個全參構造器,除static修飾的字段。@NonNull可以組合使用。

@AllArgsConstructor
public class LombokTest {
    private String name;
    @NonNull
    private String phone;
    private transient boolean flag;
    private final String finalFiled;
    private static int num;
}

編譯後的代碼(等效Java代碼)

public class LombokTest {
    private String name;
    @NonNull
    private String phone;
    private transient boolean flag;
    private final String finalFiled;
    private static int num;

    public LombokTest(String name, @NonNull String phone, boolean flag, String finalFiled) {
        if (phone == null) {
            throw new NullPointerException("phone is marked @NonNull but is null");
        } else {
            this.name = name;
            this.phone = phone;
            this.flag = flag;
            this.finalFiled = finalFiled;
        }
    }
}

小結

學到這裏常用的Lombok註解基本上都有了,如果想要根據深入的學習,建議去官網看。官網地址文章開始已經列出來了,加油!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章