閑話
“程序猿”、“碼農(nóng)”、“軟件攻城獅”拳亿,程序員這個職業(yè)現(xiàn)在已經(jīng)被這些網(wǎng)絡(luò)流行語給玩壞了。由于程序員門檻越來越低风瘦,由于“copy+改”的開發(fā)模式在業(yè)界里的流行公般,“程序員”這個曾經(jīng)神圣的詞語也似乎越來越廉價了。在人們的印象中官帘,程序員似乎就是整天在噼哩啪啦敲鍵盤的職業(yè)昧谊。我想說,噼哩啪啦敲鍵盤的那群人呢诬,其實(shí)叫打字員。程序員起碼應(yīng)該是一個動腦子的打字員尚镰。一個典型的程序員(或者叫合格的程序員)狗唉,應(yīng)該至少有80%的時間在思考和學(xué)習(xí)肾筐,最多有20%的時間用于碼字杏节。也就是說,一個不加班的程序員(假設(shè)存在)蹋半,一天應(yīng)該最多有1.6個小時在敲代碼。1.6小時能寫多少代碼份企?對于Java程序員降宅,有效的代碼量應(yīng)該差不多是100行腰根。Java恰恰是一個無效代碼很多的語言瘸恼,Java哆嗦的表達(dá)方式被無數(shù)業(yè)內(nèi)人數(shù)所詬病。因此冰啃,如何降低無效代碼量阎毅,提高開發(fā)效率抢肛,是值得每個Java程序員應(yīng)該思考的問題福稳。
開這一個系列旨在幫助大家提高開發(fā)效率半火,主要是介紹一些第三方包酌住、插件等的使用娱节。在開始之前,這里有幾個優(yōu)先級更高的通用性建議:
一是選擇一款合適的IDE(Integrate Development Environment质涛,集成開發(fā)環(huán)境)掰担。Java本身的繁瑣使得它不適合使用記事本類輕型開發(fā)工具進(jìn)行開發(fā)带饱,因此推薦使用相對重量級的IDE進(jìn)行開發(fā)勺疼。合適的開發(fā)工具可以幫助我們完成很多重復(fù)性工作,如生成常用的代碼执庐、自動導(dǎo)包轨淌、自動整理代碼格式递鹉,大幅節(jié)省碼字時間。目前市面上流行的Java IDE主要有三款:eclipse(包括在此基礎(chǔ)上衍生的MyEclipse北专、STS等)旬陡、intellij idea驶睦、netbeans匿醒。這三款產(chǎn)品各有所長,但綜合比較下來廉羔,強(qiáng)烈推薦使用intellij idea,它的智能提示效果和對各種文件類型的支持是其他兩款I(lǐng)DE所難以啟及的憋他。順便一提,它是捷克的一家叫做JetBrains的公司出品的IDE镀娶,這家公司出品的其他語言的IDE也很優(yōu)秀,如用于寫python的PyCharm揪罕、寫php的PhpStorm梯码、寫前端的WebStorm等。當(dāng)然好啰,idea的專業(yè)版是收費(fèi)的轩娶,大家都懂的,有條件的同學(xué)請支持正版坎怪。
二是建立一個自己的常用代碼庫罢坝,將一些新項(xiàng)目中常使用的代碼保存進(jìn)去,隨取隨用搅窿,可以使用github的gist功能幫助自己維護(hù)這個代碼庫嘁酿,idea有直接向github上提交gist的快捷功能,大家可以自行研究一下男应,后續(xù)文章中會有詳細(xì)介紹闹司。
三是使用新版本的JDK。在當(dāng)前語境下沐飘,建議使用JDK8以上的版本牲迫。Java8的lambda表達(dá)式和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的坐標(biāo)潘飘,如:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
</dependency>
注意在idea中使用lombok注解需要安裝lombok插件卜录,如下圖:
API干貨
Data艰毒、Value
這兩個注解是“一站式”的注解丑瞧,設(shè)計的目的是想要取代一個實(shí)體類中除成員變量聲明以外的其他所有代碼绊汹。這兩個注解都用在類的上面西乖,二者的區(qū)別在于@Data注解是按照可變類的方式生成代碼获雕,@Value注解是按照不可變類的方式生成代碼轿偎。按照文檔上的說法坏晦,@Data注解相當(dāng)于@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注解有一個可選項(xiàng)staticConstructor冠骄,可以通過將該選項(xiàng)的值設(shè)置為of來生成一個靜態(tài)的構(gòu)造器。@Value注解相當(dāng)于@Getter @FieldDefaults(makeFinal=true, level=AccessLevel.PRIVATE) @AllArgsConstructor @ToString @EqualsAndHashCode的“五合一”合集职烧,它會按照不可變類的方式去生成代碼蚀之。它會將所有成員變量聲明為private final變量足删,生成包含所有變量的構(gòu)造器以及getter失受、toString、equals和hashCode方法痪署。下面看一下實(shí)際效果兄旬,假設(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用于實(shí)現(xiàn)23種設(shè)計模式中的構(gòu)建器模式,該模式通常用于構(gòu)造包含多個成員變量的類党远。如果一個類擁有多個成員變量沟娱,創(chuàng)造包含全部成員變量的構(gòu)造器不夠靈活济似,都使用setter方法賦值又過于麻煩,這時構(gòu)建器模式就發(fā)揮作用了蛾找。構(gòu)建器模式聲明一個公有的靜態(tài)構(gòu)建器打毛,然后聲明所有成員變量的同名方法幻枉,返回構(gòu)建器熬甫,這樣就可以實(shí)現(xiàn)鏈?zhǔ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屬性進(jìn)行設(shè)置椿争,如:@Getter(value=lombok.AccessLevel.PROTECTED)
@ToString注解用于生成類的toString()方法,僅能作用于類上椅邓【澳伲可用的屬性值包括of、exclude透葛、includeFieldNames以及callSuper。of用于指定包含哪些字段萨蚕,exclude用于指定排除哪些字段岳遥,of和exclude只能存在一個。includeFieldNames是一個boolean值妻往,默認(rèn)值為true,用于指定是否包含成員變量名好渠,callSuper也是一個boolean值,默認(rèn)值為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注解的底層實(shí)現(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)致程序暗藏殺機(jī),不推薦使用沥邻。
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類型焕议。它是一項(xiàng)“未來的”java特性,是對java10中var關(guān)鍵字的一種補(bǔ)充。val關(guān)鍵字聲明的局部變量都是不可變的嗤瞎,var關(guān)鍵字修飾的變量則為可變的跛锌。
注意本質(zhì)上val還是一個注解,即
val x = 10;
相當(dāng)于
@val final int x = 10;
設(shè)計思路
有的童鞋可能會問:你上面說的這些玩意,IDE基本都可以自動生成啊,lombok到底有意義么?
下面幾點(diǎn)可以證明簡化的比生成的好:
- 可讀性。lombok去掉了getter、setter、toString胎围、equals、hashCode等一系列冗長的代碼垮抗,使得實(shí)體類看上去更加清爽冒版。
- 便于修改辞嗡。雖然IDE可以自動生成getter栋烤、setter和構(gòu)造器薯定,但是對于修改來說簡直是噩夢话侄。比如你要把一個字段從String改成int類型,你需要把對應(yīng)的getter、setter乙嘀、構(gòu)造器末购、toString、equals虎谢、hashCode通通刪掉盟榴,再重新生成。使用lombok的話這些工作都不用做了婴噩,直接修改就可以了擎场。
最佳實(shí)踐
總結(jié)一下羽德,lombok主要用于簡化Java重復(fù)代碼,提高開發(fā)效率:
- @Data迅办、@Value這兩個合集用于簡化實(shí)體類的重復(fù)代碼宅静,一個可變一個不可變,在一般情況下這倆注解就夠用了站欺。但是如果要訂制getter姨夹、setter等方法,就需要使用各個注解了镊绪。
- @Builder注解用于實(shí)現(xiàn)構(gòu)造器模式匀伏,方便多成員變量的實(shí)體類的構(gòu)造。
- @Cleanup注解用于關(guān)閉資源蝴韭,功能類似于try-with-resources特性够颠。
- @NonNull注解用于對參數(shù)和成員變量進(jìn)行空值檢測。
- @Synchronized注解用于更優(yōu)雅地實(shí)現(xiàn)同步鎖榄鉴。
- @val注解用于體驗(yàn)java未來的功能——動態(tài)變量類型履磨。