使用註解簡化Java開發中的樣板代碼——Lombok框架

前言

不少人詬病Java過於臃腫,重複,有人評價寫Java就像寫作文,一個單詞一個單詞的往下敲,所以後來在JVM平臺上也誕生了許多以改善Java爲目標的語言,如Scala,Kotlin。我覺得,Java這一點既可以被評價爲臃腫,也可以認爲是中正平和,嚴謹有加,對於新手程序員還是非常友好的,不必在莫名奇妙的縮寫方法和指代參數中“尋章摘句”,對面向對象還是三分認識的時候,嚴謹的編碼約束是一種行之有效的引導。

不過,對於在碼農屆浸淫已久的老手,對比那些後起之秀的新語言,Java確實在方方面面顯得有些疊牀架屋。例如,在開發過程中,我們通常都會定義大量的JavaBean,然後通過IDE去生成其屬性的構造器、getter、setter、equals、hashcode、toString方法,當要對某個屬性進行改變時,比如命名、類型等,都需要重新去生成上面提到的這些方法,因此被許多苦於此類的人詬病爲爲了面向對象而特別寫成面向對象,那麼有沒有辦法可以簡化此類操作呢,有的。今天就介紹一個框架,可以一定程度上簡化Java的繁雜的代碼。

使用lombok框架簡化Java開發中的樣板代碼

Lombok是什麼?

按官網的定義:

Lombok工程是一個自動插入你的編譯器和構建工具的Java類庫,使你的Java代碼更加簡潔。使用後,再也不需要寫getter或者equals等重複內容,並提供快速訪問Java對象的方法。

其實  Lombok就是一個使用註解形式來簡化Java開發中一些必須有但是寫的多了就很會感覺繁瑣又臃腫的Java代碼的工具。

最常用的就是給一個類添加構造器getter、setter、equals、hashcode、toString等必須方法

爲什麼要使用lombok

一個普通的類

博客blog 只有三個成員屬性

看着還是很簡潔的

public class blog
{
    private String name;

    private String writer;

    private long size;      

}

我們按最普通的 只添加get和set方法

public class blog {

    private String name;

    private String writer;

    private long size;

    public String getName() {
        return name;
    }

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

    public String getWriter() {
        return writer;
    }

    public void setWriter(String writer) {
        this.writer = writer;
    }

    public long getSize() {
        return size;
    }

    public void setSize(long size) {
        this.size = size;
    }
}

哦 已經有點暈了 那再加上含參構造呢

public class blog {

    private String name;

    private String writer;

    private long size;

    public blog(String name, String writer, long size) {
        this.name = name;
        this.writer = writer;
        this.size = size;
    }

    public blog(String name) {
        this.name = name;
    }

    public blog(long size) {
        this.size = size;
    }

    public String getName() {
        return name;
    }

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

    public String getWriter() {
        return writer;
    }

    public void setWriter(String writer) {
        this.writer = writer;
    }

    public long getSize() {
        return size;
    }

    public void setSize(long size) {
        this.size = size;
    }
}

呃 確實很臃腫 看着都頭疼

只有三個成員屬性
爲了操作他們竟然需要增加這麼多方法

然而這還不是個完善的JavaBean

缺少equals hashcode toString等方法

添加上這些後

public class blog {

    private String name;

    private String writer;

    private long size;

    public blog(String name, String writer, long size) {
        this.name = name;
        this.writer = writer;
        this.size = size;
    }

    public blog(String name) {
        this.name = name;
    }

    public blog(long size) {
        this.size = size;
    }

    public String getName() {
        return name;
    }

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

    public String getWriter() {
        return writer;
    }

    public void setWriter(String writer) {
        this.writer = writer;
    }

    public long getSize() {
        return size;
    }

    public void setSize(long size) {
        this.size = size;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof blog)) return false;

        blog blog = (blog) o;

        if (getSize() != blog.getSize()) return false;
        if (!getName().equals(blog.getName())) return false;
        return getWriter().equals(blog.getWriter());
    }

    @Override
    public int hashCode() {
        int result = getName().hashCode();
        result = 31 * result + getWriter().hashCode();
        result = 31 * result + (int) (getSize() ^ (getSize() >>> 32));
        return result;
    }

    @Override
    public String toString() {
        return "blog{" +
                "name='" + name + '\'' +
                ", writer='" + writer + '\'' +
                ", size=" + size +
                '}';
    }
}

哦 這可真令人煩躁
一個類僅僅是三個成員屬性
要達到完善的可用狀態
就要添加如此之多的方法
真是令人頭特
無怪許多人攻擊java過於臃腫
畢竟C# 都提供了

public string name{get;set;}

這種簡化方案

如果使用lombok呢

@Data
public class blog {

    private String name;

    private String writer;

    private long size;

}

只要這樣就可以了
沒錯 僅僅只是添加一個@Data註解
就省去了如此之多的樣板代碼

這個blog類看上去也是十分的清晰簡介
突出了最重要的部分——他的成員屬性

lombok既簡化了樣板代碼 ,又突出了一個類(JavaBean)
的核心內容 所以十分有使用的必要

有人問爲什麼要在意JavaBean的樣板方法內容多少
反正都是自動生成的

這個問題最後解答

如何使用lombok

引入依賴

Maven

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

Gradle

provided group: 'org.projectlombok', name: 'lombok', version: '1.16.18'

安裝開發環境插件

lombok使用註解生成方法 所以沒有顯示的方法代碼
如果不配置開發環境插件 編譯器會報錯的

以Intellij idea爲例

1.啓用註解處理器
這裏寫圖片描述
2.安裝lombok插件
安裝lombok插件

注意這裏我已經安裝了 所以是update 沒安裝的話是install
安裝完後重啓下開發環境

這樣就可以在開發中使用lombok的註解了

lombok的使用

lombok提供了許多註解來簡化Java開發中的樣板代碼
最常用的有
@Data
@Getter @Setter
@ToString
@EqualsAndHashCode
@RequiredConstructor
@AllArgsConstructor
@NoArgsConstructor
@Builder

基本都是添加在JavaBean上用以簡化樣板代碼的

@Getter@Setter

作用於屬性上,自動生成get,set方法.

public class Student{
    @Getter@Setter
    private String name;
}

效果就等同於

public class Student{

    private String name;

    public String getName() {
            return name;
    }

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

也可以添加在類上

@Getter@Setter
public class Student{
    private String name;
    private String age;
}

等效於

public class Student {
    private String name;
    private String age;

    public String getName() {
        return name;
    }

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

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }
}

所以 一般此類註解都是直接添加在類上

@ToString

用戶自定義的類 如果不重寫ToString方法
則類的ToString就是默認Object的ToString實現
返回一個類似Student@78951的用戶地址引用標識符
顯而易見 我們並不希望看到這個 而是希望顯示出類的數值內容

@ToString
public class Student {
    private String name;
    private String age;

}

使用lombok的@ToString註解
等效於重寫ToString方法

public class Student {
    private String name;
    private String age;

    public String toString() {
        return "Student(name=" + this.name + ", age=" + this.age + ")";
    }
}

可以看到 重寫後的toString方法已經提供了我們想要知道的類信息

@EqualsAndHashCode

重寫equals和hashcode方法 可以方便的比較類的值是否相等

@EqualsAndHashCode
public class Student {
    private String name;
    private String age;
}

等效於

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

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

                return true;
            }
        }
    }

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

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

看以看到 lombok註解實現了一整套的值相等的判斷算法
相比方法內容全部顯示展現的Javabean 添加註解的類代碼非常簡潔

@RequiredConstructor
@AllArgsConstructor
@NoArgsConstructor

這三個註解可以說是見文知意 分別是含參構造 全參構造 和無參構造
根據需要添加即可

@Data

一個JavaBean要完善可用
那就必須要有構造 get set方法 並重寫toString equals hashcode方法

使用lombok 也要分別添加 以上註解

@Getter
@Setter
@RequiredArgsConstructor
@EqualsAndHashCode
@ToString
public class Student {
    private String name;
    private String age;

}

未免還是顯得繁瑣了
既然這些都是必須的 不如簡化到底

這就有了最開始我們看到的@Data註解

@Data
public class Student {
    private String name;
    private String age;

}

只用添加一個@Data註解
所有的必備方法便準備完畢

@Value

此註解用於快速定義一個不可變類 其他功能和@Data是一樣的
就是說 添加了@Value的類
其類被final修飾 成員屬性也增加了final修飾符

@Value
public class Student {
    private String name;
    private String age;
}

編譯後

public final class Student {
    private final String name;
    private final String age;

    @ConstructorProperties({"name", "age"})
    public Student(String name, String age) {
        this.name = name;
        this.age = age;
    }

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

    public String getAge() {
        return this.age;
    }

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

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

            return true;
        }
    }

    public int hashCode() {
        int PRIME = true;
        int result = 1;
        Object $name = this.getName();
        int result = result * 59 + ($name == null?43:$name.hashCode());
        Object $age = this.getAge();
        result = result * 59 + ($age == null?43:$age.hashCode());
        return result;
    }

    public String toString() {
        return "Student(name=" + this.getName() + ", age=" + this.getAge() + ")";
    }
}

可以看到和@Data 一樣 被@Value修飾的類也重寫了equals hashcode tostring
只是此類本身和類的成員屬性都是不可變的 同時也沒有提供set方法
通過構造函數初始化後就不能更改了 同時類也不能被繼承
所以@Value註解功能是快速生成不可變類

@Builder

此註解會爲類生成一個構建器方法
《Effective Java》中提出一個觀點 構建器比構造函數更有效 更清晰
我是比較贊同此觀點的 尤其是在一些成員函數特別多的大類中
構建器比構造函數要好用的多
不過現在的Java中並沒有提供構建器
遺憾的是大多數開發環境也沒有提供自動生成構建器的功能
所以很少見到使用構建器
但是lombok提供了一個解決方案 添加一個@Builder註解
構建器方法便可用了

@Builder
public class Student {
    private String name;
    private String age;

}

編譯後的代碼

public class Student {
    private String name;
    private String age;

    @ConstructorProperties({"name", "age"})
    Student(String name, String age) {
        this.name = name;
        this.age = age;
    }

    public static StudentBuilder builder() {
        return new StudentBuilder();
    }
    public static class StudentBuilder {
        private String name;
        private String age;

        StudentBuilder() {
        }

        public Student.StudentBuilder name(String name) {
            this.name = name;
            return this;
        }

        public Student.StudentBuilder age(String age) {
            this.age = age;
            return this;
        }

        public Student build() {
            return new Student(this.name, this.age);
        }

        public String toString() {
            return "Student.StudentBuilder(name=" + this.name + ", age=" + this.age + ")";
        }
    }
}

類內部已經生成一個構建器方法 包含了各成員屬性的構建

調用的時候

    Student student=Student.builder()
                .name("student")
                .age("18")
                .build();

非常的清晰 新建一個類 鏈式調用其賦值方法
可以明確的知道是賦值給哪個對應的成員屬性
而不是構造函數模糊的形參列表

@Builder還有一個相關注解 @Singular
用於標註在集合類型的成員屬性上
可以防止重複調用給此成員屬性賦值
因爲集合類重複調用賦值就是向此集合內部添加數據
而不是替換

日誌

lombok還提供了日誌類註解 包含
@CommonsLog
@Log
@Log4j
@Log4j2
@XSlf4j
@JBossLog
囊括了大多數Java中用到的日誌組件

以Slf4j爲例

@Slf4j
public class Student {
    private String name;
    private String age;

}

等價於

public class Student {
    private static final Logger log = LoggerFactory.getLogger(Student.class);
    private String name;
    private String age;
}

將與業務無關的日誌功能從代碼體中剝離出來
用註解標示 既減少了代碼量
還可以顯著減少錯誤
因爲

private static final Logger log = LoggerFactory.getLogger(Student.class);

這行代碼 大多數人是懶得寫的
都是以複製粘貼爲主
經常把getLogger(Student.class)裏的類也粘貼過來
造成日誌錯誤

下面介紹一些一般不用在Model上的註解
這些註解可謂是 真·奇技淫巧

@NoNull

添加在成員變量或者形參上
用於檢測變量是否爲空
如果爲空 則拋出一個空指針異常
這個不建議使用
成員屬性的判空可以使用Bean Validation來做
拋出空指針一般是需要避免的

@Synchronized

自動添加同步鎖

private DateFormat format = new SimpleDateFormat("YYYY-MM-dd");

@Synchronized
public String synchronizedFormat(Date date) {
    return format.format(date);
}

等價於

private final Object $lock = new Object[0];
private DateFormat format = new SimpleDateFormat("YYYY-MM-dd");

public String synchronizedFormat(Date date) {
    synchronized ($lock) {
        return format.format(date);
    }
}

可以看到該方法並不是直接作用在方法上
而是鎖代碼塊

@Cleanup

自動關閉流

public class Cleanups {
    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);
        }
    }
}  

等效於

 class CleanupExample {
    public static void main(String[] args) throws IOException {
        InputStream in = new FileInputStream(args[0]);
        try {
            OutputStream out = new FileOutputStream(args[1]);
            try {
                byte[] b = new byte[10000];
                while (true) {
                    int r = in.read(b);
                    if (r == -1) break;
                    out.write(b, 0, r);
                }
            } finally {
                if (out != null) {
                    out.close();
                }
            }
        } finally {
            if (in != null) {
                in.close();
            }
        }
    }
} 

其他非註解功能

var/val

用過Ktolin的都知道 Ktolin提供的推定類val var
C#裏也有 推定類var 和linq結合很方便
推定類是編譯器自動根據上下推斷類型
有些不符合java面向對象 強類型的格局

不過看看其他各種語言都提供了
java的一切爲了面向對象的格局本身也在做改變
JEP268標準也引入了局部變量類型推斷
據說Java10就會引入推定類var

lombok就提供了這一功能

val 不可變推定類 初始化後就不能變化了 和ktolin裏面的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());
        }
    }
}

可以看到val類型是編譯器根據上下文自動推斷出來的

var 可變推定類 可以通過重新分配來更改爲另一個值的變量,和java中聲明正常變量的方式一樣。
目前lombok還沒有提供var的正式內容 但在官網上已經有了闡述

預測和ktolin種的var類相同的用法

配置項

在程序根目錄下建立lombok.config文件就可以配置lombok的一些選項
這裏寫圖片描述
如果使用idea開發環境 添加了lombok支持的項目 配置項是可以智能提示的

常用的有
通用的
lombok.*.falgUsage 可以設置使用lombok時是否提示錯誤 有error waring等

get/set的設置項
lombok.accessors.chain true爲允許鏈式調用 默認false不允許
lombok.accessors.fluent true爲get set方法首字符小寫 默認false 首字母大寫

最後

有觀點反對使用lombok 聲稱lombok的自動代碼生成影響了維護性

個人認爲 這個觀點放在後面那幾個帶有點“奇技淫巧”意思的註解上
可能還有點道理

對於JavaBean上應用的註解和日誌組建類註解來說 純屬杞人憂天
之前說過 有人覺得model類的必備方法 都可以用環境自動生成
用不着簡化

此言差矣 如果做過大型項目就知道
多人團隊的情況下 指望其他人優秀的維護每個模型類就是異想天開
類內部方法和變量亂七八糟的攪合在一起
根本不能一目瞭然的看清楚有多少屬性 況且
一個class的屬性是一直變化的 今天可能增加一個字段 明天可能刪除一個字段。
每次變化都需要修改對應的模板代碼
還有不過避免的出現超級類
成員屬性多的驚人 和方法混在一起 簡直就是噩夢

《Effective Java》指出 每個用戶自定義的類都應該遵循Java的規約
重寫equals和hashcode方法 確保每個類都是可比較的

問題是 很多人根本就不重寫equals和hashcode方法
埋下了無數地雷
尤其是項目太大 成員太多的情況下 根本沒人力去檢視每個提交的代碼

如果使用了lombok的@Data註解呢 這一切都不是問題了
日誌組件也是同理
複製粘貼日誌啓動段代碼
造成了無數錯誤日誌
使用@Slf4j/@log4j 輕鬆解決

目前Spring官方的Spring Initializr都出現了lombok的自動配置
還有什麼不使用的理由呢?!

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