提高開發(fā)效率的奇技(一)lombok

閑話

“程序猿”、“碼農(nóng)”、“軟件攻城獅”,程序員這個職業(yè)現(xiàn)在已經(jīng)被這些網(wǎng)絡(luò)流行語給玩壞了。由于程序員門檻越來越低,由于“copy+改”的開發(fā)模式在業(yè)界里的流行,“程序員”這個曾經(jīng)神圣的詞語也似乎越來越廉價了。在人們的印象中,程序員似乎就是整天在噼哩啪啦敲鍵盤的職業(yè)。我想說,噼哩啪啦敲鍵盤的那群人,其實叫打字員。程序員起碼應(yīng)該是一個動腦子的打字員。一個典型的程序員(或者叫合格的程序員),應(yīng)該至少有80%的時間在思考和學(xué)習(xí),最多有20%的時間用于碼字。也就是說,一個不加班的程序員(假設(shè)存在),一天應(yīng)該最多有1.6個小時在敲代碼。1.6小時能寫多少代碼?對于Java程序員,有效的代碼量應(yīng)該差不多是100行。Java恰恰是一個無效代碼很多的語言,Java哆嗦的表達方式被無數(shù)業(yè)內(nèi)人數(shù)所詬病。因此,如何降低無效代碼量,提高開發(fā)效率,是值得每個Java程序員應(yīng)該思考的問題。

開這一個系列旨在幫助大家提高開發(fā)效率,主要是介紹一些第三方包、插件等的使用。在開始之前,這里有幾個優(yōu)先級更高的通用性建議:

一是選擇一款合適的IDE(Integrate Development Environment,集成開發(fā)環(huán)境)。Java本身的繁瑣使得它不適合使用記事本類輕型開發(fā)工具進行開發(fā),因此推薦使用相對重量級的IDE進行開發(fā)。合適的開發(fā)工具可以幫助我們完成很多重復(fù)性工作,如生成常用的代碼、自動導(dǎo)包、自動整理代碼格式,大幅節(jié)省碼字時間。目前市面上流行的Java IDE主要有三款:eclipse(包括在此基礎(chǔ)上衍生的MyEclipse、STS等)、intellij idea、netbeans。這三款產(chǎn)品各有所長,但綜合比較下來,強烈推薦使用intellij idea,它的智能提示效果和對各種文件類型的支持是其他兩款I(lǐng)DE所難以啟及的。順便一提,它是捷克的一家叫做JetBrains的公司出品的IDE,這家公司出品的其他語言的IDE也很優(yōu)秀,如用于寫python的PyCharm、寫php的PhpStorm、寫前端的WebStorm等。當然,idea的專業(yè)版是收費的,大家都懂的,有條件的同學(xué)請支持正版。

二是建立一個自己的常用代碼庫,將一些新項目中常使用的代碼保存進去,隨取隨用,可以使用github的gist功能幫助自己維護這個代碼庫,idea有直接向github上提交gist的快捷功能,大家可以自行研究一下,后續(xù)文章中會有詳細介紹。

三是使用新版本的JDK。在當前語境下,建議使用JDK8以上的版本。Java8的lambda表達式和Stream功能可以大幅提高開發(fā)效率。每個版本的JDK設(shè)計時,都會考慮簡化開發(fā)、提高效率,比如java7的diamond語法(Map<String, Object> map = new HashMap<>(),等號后面的尖括號內(nèi)不用再指定類型),再比如后續(xù)java10中的var使得java向動態(tài)類型語言的方向發(fā)展。因此,掌握和使用新版本的JDK幾乎總能提升你的開發(fā)效率。

四是學(xué)習(xí)一門腳本語言,比如python。一方面平時常用的一些小的功能可以用腳本語言快速開發(fā)出來,另一方面可以使用腳本語言結(jié)合模板開發(fā)一些代碼生成器。有人說過,超過90秒的重復(fù)性工作就應(yīng)該寫腳本來完成,試想你的代碼能代替你工作,你是不就可以坐享其成了?

這個系列的第一部分講lombok,它是一個旨在減少重復(fù)性代碼的第三方包,它的設(shè)計思路是通過一系列注解來自動生成代碼。

引入方式

在maven的pom.xml文件中添加lombok的坐標,如:

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

注意在idea中使用lombok注解需要安裝lombok插件,如下圖:


idea_lombok_plugin.png

API干貨

Data、Value

這兩個注解是“一站式”的注解,設(shè)計的目的是想要取代一個實體類中除成員變量聲明以外的其他所有代碼。這兩個注解都用在類的上面,二者的區(qū)別在于@Data注解是按照可變類的方式生成代碼,@Value注解是按照不可變類的方式生成代碼。按照文檔上的說法,@Data注解相當于@Getter @Setter @RequiredArgsConstructor @ToString @EqualsAndHashCode注解的“五合一”合集,它會根據(jù)成員變量自動生成相應(yīng)的get方法、set方法、構(gòu)造器、toString方法以及equals和hashCode方法。注意final修飾的成員變量不會生成相應(yīng)的set方法,也不會參與構(gòu)造器的生成,transient修飾的成員變量則不會參與equals和hashCode方法的生成。@Data注解有一個可選項staticConstructor,可以通過將該選項的值設(shè)置為of來生成一個靜態(tài)的構(gòu)造器。@Value注解相當于@Getter @FieldDefaults(makeFinal=true, level=AccessLevel.PRIVATE) @AllArgsConstructor @ToString @EqualsAndHashCode的“五合一”合集,它會按照不可變類的方式去生成代碼。它會將所有成員變量聲明為private final變量,生成包含所有變量的構(gòu)造器以及getter、toString、equals和hashCode方法。下面看一下實際效果,假設(shè)有一個Student類,包含id、name、age、grade字段:

@Data
public class Student{
    private String id;
    private final String name;
    private transient int age;
    private int grade;
}

等價于

import java.beans.ConstructorProperties;

public class Student {
    private String id;
    private final String name;
    private transient int age;
    private int grade;

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

    public String getId() {
        return this.id;
    }

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

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

    public int getGrade() {
        return this.grade;
    }

    public void setId(String id) {
        this.id = id;
    }

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

    public void setGrade(int grade) {
        this.grade = grade;
    }

    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 {
                label39: {
                    Object this$id = this.getId();
                    Object other$id = other.getId();
                    if (this$id == null) {
                        if (other$id == null) {
                            break label39;
                        }
                    } else if (this$id.equals(other$id)) {
                        break label39;
                    }

                    return false;
                }

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

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

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

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

    public String toString() {
        return "Student(id=" + this.getId() + ", name=" + this.getName() + ", age=" + this.getAge() + ", grade=" + this.getGrade() + ")";
    }
}
@Value
public class Student {
    private String id;
    private String name;
    private transient int age;
    private int grade;
}

則等價于

import java.beans.ConstructorProperties;

public final class Student {
    private final String id;
    private final String name;
    private final transient int age;
    private final int grade;

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

    public String getId() {
        return this.id;
    }

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

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

    public int getGrade() {
        return this.grade;
    }

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

            label29: {
                Object this$name = this.getName();
                Object other$name = other.getName();
                if (this$name == null) {
                    if (other$name == null) {
                        break label29;
                    }
                } else if (this$name.equals(other$name)) {
                    break label29;
                }

                return false;
            }

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

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

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

注意使用@Value注解時,成員變量不能直接聲明為final類型,否則會有編譯錯誤。

Builder

@Builder用于實現(xiàn)23種設(shè)計模式中的構(gòu)建器模式,該模式通常用于構(gòu)造包含多個成員變量的類。如果一個類擁有多個成員變量,創(chuàng)造包含全部成員變量的構(gòu)造器不夠靈活,都使用setter方法賦值又過于麻煩,這時構(gòu)建器模式就發(fā)揮作用了。構(gòu)建器模式聲明一個公有的靜態(tài)構(gòu)建器,然后聲明所有成員變量的同名方法,返回構(gòu)建器,這樣就可以實現(xiàn)鏈式調(diào)用,最后通過build()方法調(diào)用私有構(gòu)造器,完成對象的創(chuàng)建,從而簡化代碼。下面看一下官方文檔里的例子:

@Builder
class Example {
    private int foo;
    private final String bar;
}

等價于

class Example {
    private int foo;
    private final String bar;

    Example(int foo, String bar) {
        this.foo = foo;
        this.bar = bar;
    }

    public static Example.ExampleBuilder builder() {
        return new Example.ExampleBuilder();
    }

    public static class ExampleBuilder {
        private int foo;
        private String bar;

        ExampleBuilder() {
        }

        public Example.ExampleBuilder foo(int foo) {
            this.foo = foo;
            return this;
        }

        public Example.ExampleBuilder bar(String bar) {
            this.bar = bar;
            return this;
        }

        public Example build() {
            return new Example(this.foo, this.bar);
        }

        public String toString() {
            return "Example.ExampleBuilder(foo=" + this.foo + ", bar=" + this.bar + ")";
        }
    }
}

@Builder注解可以用于類、方法和構(gòu)造器上,如果@Builder注解作用于方法上,生成的build()方法會調(diào)用該方法,不可以有兩個方法同時使用@Builder注解。

Getter、Setter、ToString、EqualsAndHashCode

@Getter、@Setter、@ToString和@EqualsAndHashCode注解分別用于給類的成員變量生成get方法、set方法、給類生成toString()、equals和hashCode()方法。
其中@Getter和@Setter注解可以作用于類上或者成員變量上,如果作用于類上,所有的成員變量均自動生成get和set方法(final變量無set方法)。注意如果變量是boolean類型,生成的get方法叫做isXXX,如boolean good的get方法為isGood()。如果想生成非public的get或set方法,可以將注解的value屬性進行設(shè)置,如:@Getter(value=lombok.AccessLevel.PROTECTED)
@ToString注解用于生成類的toString()方法,僅能作用于類上。可用的屬性值包括of、exclude、includeFieldNames以及callSuper。of用于指定包含哪些字段,exclude用于指定排除哪些字段,of和exclude只能存在一個。includeFieldNames是一個boolean值,默認值為true,用于指定是否包含成員變量名,callSuper也是一個boolean值,默認值為false,如果設(shè)置為true,在生成的toString方法中將包含父類的toString結(jié)果。
@EqualsAndHashCode方法用于生成類的equals()和hashCode()方法,只能作用于類上,可用的屬性值類似于@ToString,也包括of、exclude以及callSuper。transient修飾的成員變量不參與生成equals()和hashCode()方法。
下面看一些例子:


@ToString(includeFieldNames=false, of={"id", "grade"})
@EqualsAndHashCode(exclude={"name"})
public class Student {
    @Getter(value=lombok.AccessLevel.PROTECTED)
    private String id;
    @Setter
    private String name;
    private transient int age;
    private int grade;
}

等價于

public class Student {
    private String id;
    private String name;
    private transient int age;
    private int grade;

    public Student() {
    }

    public String toString() {
        return "Student(" + this.getId() + ", " + this.grade + ")";
    }

    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$id = this.getId();
                Object other$id = other.getId();
                if (this$id == null) {
                    if (other$id == null) {
                        return this.grade == other.grade;
                    }
                } else if (this$id.equals(other$id)) {
                    return this.grade == other.grade;
                }

                return false;
            }
        }
    }

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

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

    protected String getId() {
        return this.id;
    }

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

NoArgsConstructor、RequiredArgsConstructor、AllArgsConstructor

@NoArgsConstructor、@RequiredArgsConstructor和@AllArgsConstructor都作用于類上,分別用于生成無參構(gòu)造器、指定參數(shù)構(gòu)造器和全參構(gòu)造器。@RequiredArgsConstructor指定的參數(shù)是指final修飾的成員變量以及有約束條件(如用@NonNull修飾)的成員變量。

Cleanup

@Cleanup注解用在局部變量上,用于關(guān)閉指定的資源,如輸入輸出流。@Cleanup注解的底層實現(xiàn)方式是在finally塊中調(diào)用資源的close方法,如果關(guān)閉資源的方法名不為close,可以使用注解的value屬性指定方法名,注意指定的方法必須是沒有參數(shù)的。看下官方文檔中的例子:

public void copyFile(String in, String out) throws IOException {
    @Cleanup FileInputStream inStream = new FileInputStream(in);
    @Cleanup FileOutputStream outStream = new FileOutputStream(out);
    byte[] b = new byte[65536];
    while (true) {
        int r = inStream.read(b);
        if (r == -1) break;
        outStream.write(b, 0, r);
    }
}

等價于

public void copyFile(String in, String out) throws IOException {
   @Cleanup FileInputStream inStream = new FileInputStream(in);
   try {
       @Cleanup FileOutputStream outStream = new FileOutputStream(out);
       try {
           byte[] b = new byte[65536];
           while (true) {
               int r = inStream.read(b);
               if (r == -1) break;
               outStream.write(b, 0, r);
           }
       } finally {
           if (out != null) out.close();
       }
   } finally {
       if (in != null) in.close();
   }

NonNull

@NonNull注解作用于類的成員變量、方法、參數(shù)以及局部變量上。如果放在參數(shù)上,lombok將在方法/構(gòu)造器方法體內(nèi)最開始的位置插入空值檢測的語句,如果變量值為null,將拋出空指針異常。如果放在成員變量上,任何為該變量賦值的方法(如set方法和構(gòu)造器)中將生成空值檢測語句。在方法和局部變量上加入該注解似乎沒有什么作用。

SneakyThrows

@SneakyThrows注解作用于方法或構(gòu)造器上,用于將受檢異常轉(zhuǎn)換為非受檢異常,該方法可能導(dǎo)致程序暗藏殺機,不推薦使用。

Synchronized

@Synchronized注解作用于方法上,類似于synchronized關(guān)鍵字,它生成一個私有的變量,將同步鎖加在私有變量上,這樣可以避免其他不受你控制的代碼干擾你的線程管理。借用一個別人整理的代碼:

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

等價于

public class SynchronizedExample {
  private static final Object $LOCK = new Object[0];
  private final Object $lock = new Object[0];
  private final Object readLock = new Object();
  
  public static void hello() {
    synchronized($LOCK) {
      System.out.println("world");
    }
  }
  
  public int answerToLife() {
    synchronized($lock) {
      return 42;
    }
  }
  
  public void foo() {
    synchronized(readLock) {
      System.out.println("bar");
    }
  }
}

val

@val應(yīng)該是所有注解里最特殊的一個了。首先它長得就不太一樣,首字母小寫讓它看上去不那么像注解。再者它的用法也不太一樣,它使用的時候不需要加“@”符號,直接

val s = "code";

即可。這個注解用于局部變量聲明時自動推斷變量的類型,如上述代碼將s推斷為String類型。它是一項“未來的”java特性,是對java10中var關(guān)鍵字的一種補充。val關(guān)鍵字聲明的局部變量都是不可變的,var關(guān)鍵字修飾的變量則為可變的。
注意本質(zhì)上val還是一個注解,即

val x = 10;

相當于

@val final int x = 10;

設(shè)計思路

有的童鞋可能會問:你上面說的這些玩意,IDE基本都可以自動生成啊,lombok到底有意義么?
下面幾點可以證明簡化的比生成的好:

  • 可讀性。lombok去掉了getter、setter、toString、equals、hashCode等一系列冗長的代碼,使得實體類看上去更加清爽。
  • 便于修改。雖然IDE可以自動生成getter、setter和構(gòu)造器,但是對于修改來說簡直是噩夢。比如你要把一個字段從String改成int類型,你需要把對應(yīng)的getter、setter、構(gòu)造器、toString、equals、hashCode通通刪掉,再重新生成。使用lombok的話這些工作都不用做了,直接修改就可以了。

最佳實踐

總結(jié)一下,lombok主要用于簡化Java重復(fù)代碼,提高開發(fā)效率:

  • @Data、@Value這兩個合集用于簡化實體類的重復(fù)代碼,一個可變一個不可變,在一般情況下這倆注解就夠用了。但是如果要訂制getter、setter等方法,就需要使用各個注解了。
  • @Builder注解用于實現(xiàn)構(gòu)造器模式,方便多成員變量的實體類的構(gòu)造。
  • @Cleanup注解用于關(guān)閉資源,功能類似于try-with-resources特性。
  • @NonNull注解用于對參數(shù)和成員變量進行空值檢測。
  • @Synchronized注解用于更優(yōu)雅地實現(xiàn)同步鎖。
  • @val注解用于體驗java未來的功能——動態(tài)變量類型。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 在面向?qū)ο缶幊讨斜夭豢缮傩枰诖a中定義對象模型,而在基于Java的業(yè)務(wù)平臺開發(fā)實踐中尤其如此。相信大家在平時開發(fā)...
    消失er閱讀 12,946評論 15 150
  • 前言: 逛開源社區(qū)的時候無意發(fā)現(xiàn)的,用了一段時間,覺得還可以,特此推薦一下。 lombok 提供了簡單的注解的形式...
    OzanShareing閱讀 4,486評論 0 7
  • 【打卡接龍】:請家人安順序接下去 公司:浙江省東陽市東元食品有限公司 姓名:許亮亮 【日精進打卡第18天】 【知~...
    許亮亮_2a75閱讀 86評論 0 0
  • 在勝間和代的書中,談了個水平思考力,還談了一個垂直思考力,這兩個組合起來,就是立體思考力。垂直思考力就是多問為什么...
    鴨梨山大哎閱讀 432評論 0 1
  • 猝不及防,我邂逅了你----靜好書院。 我親昵地給你起了一個中意的別名:心中良人。 我悄悄地愛著你,你也默默地喜歡...
    涂糊蟲閱讀 1,636評論 1 3

友情鏈接更多精彩內(nèi)容