前言
lombok是一個編譯級別的插件,它可以在項目編譯的時候生成一些代碼。在很多工具類的項目中都有這個功能。比如dagger。
通俗的說,lombok可以通過注解來標示生成getter
settter
等代碼。我們自然可以通過編譯器比如IDEA的Generate
生成,為啥要用這個?
在項目開發階段,一個class的屬性是一直變化的,今天可能增加一個字段,明天可能刪除一個字段。每次變化都需要修改對應的模板代碼。另外,有的class的字段超級多,多到一眼看不完。如果加上模板代碼,更難一眼看出來。更有甚者,由於字段太多,想要使用builder來創建。手動創建builder和字段和原來的類夾雜在一起,看起來真的難受。lombok的@Builder
即可解決這個問題。
引入
引入就是加入lombok
的jar包。
在maven中
直接加入依賴
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.20</version>
</dependency>
在gradle中
這里比較麻煩,需要添加一個編譯時生成代碼的插件。gradle里有幾個這樣的插件。但為了簡化過程,lombok提供了新插件。
首先,添加一個plugin
plugins {
id 'io.franzbecker.gradle-lombok' version '1.11'
}
然后,就可以了。還可以配置lombok的版本:
lombok { // optional: values below are the defaults
version = "1.16.20"
sha256 = ""
}
IntelIJ IDEA 插件
在IDEA里使用需要添加一個插件。在插件里搜索lombok,安裝,重啟。
IDEA里需要在設置中啟用annotation processors。
如果升級了InteliJ IDEA, 可能出現lombok不能用了,右鍵更新lombok plugin,重啟即可。
基本用法
測試代碼: https://github.com/Ryan-Miao/someTest/tree/master/src/main/java/com/test/lombok
Geter Setter
最簡單的,最常用的,最直觀的使用就是getter setter方法。
package com.test.lombok;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
/**
* Created by Ryan Miao on 1/18/18.
*/
public class GetterSetterExample {
/**
* Age of the person. Water is wet.
*
* @param age New value for this person's age. Sky is blue.
* @return The current value of this person's age. Circles are round.
*/
@Getter
@Setter
private int age = 10;
@Getter
@Setter
private boolean active;
@Getter
@Setter
private Boolean none;
@Getter
@Setter
private Date date;
/**
* Name of the person.
* -- SETTER --
* Changes the name of this person.
*
* @param name The new value.
*/
@Setter(AccessLevel.PROTECTED) private String name;
@Override public String toString() {
return String.format("%s (age: %d)", name, age);
}
public static void main(String[] args) {
GetterSetterExample example = new GetterSetterExample();
example.setActive(true);
example.setAge(123);
example.setDate(new Date());
example.setName("abc");
example.setNone(false);
Date date = example.getDate();
Boolean none = example.getNone();
boolean active = example.isActive();
}
}
簡單使用沒有問題,深入一點可以看到有些特殊設定。比如javadoc.
Getter
聲明創建getter方法;Setter
聲明創建setter方法;@Setter(AccessLevel.PROTECTED)
可以添加參數,指定權限為私有;- Attention!關於
boolean
的set前綴都是set,但getter不同,小寫的boolean
,即基本類型,前綴是is
;Boolean
,即包裝類型,前綴是get
;
編譯后的結果如下:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.test.lombok;
import java.util.Date;
public class GetterSetterExample {
private int age = 10;
private boolean active;
private Boolean none;
private Date date;
private String name;
public GetterSetterExample() {
}
public String toString() {
return String.format("%s (age: %d)", this.name, this.age);
}
public static void main(String[] args) {
GetterSetterExample example = new GetterSetterExample();
example.setActive(true);
example.setAge(123);
example.setDate(new Date());
example.setName("abc");
example.setNone(false);
Date date = example.getDate();
Boolean none = example.getNone();
boolean active = example.isActive();
}
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
public boolean isActive() {
return this.active;
}
public void setActive(boolean active) {
this.active = active;
}
public Boolean getNone() {
return this.none;
}
public void setNone(Boolean none) {
this.none = none;
}
public Date getDate() {
return this.date;
}
public void setDate(Date date) {
this.date = date;
}
protected void setName(String name) {
this.name = name;
}
}
ToString
雖然ToString在生產環境貌似沒什么卵用。但是,很多情況下,我們還是需要這個的。因為記log。不想每次看log的時候是一串@地址,那就好好把toString()加上。
package com.test.lombok;
import lombok.Setter;
import lombok.ToString;
/**
* Created by Ryan Miao on 1/18/18.
*/
@Setter
@ToString(exclude="id")
public class ToStringExample {
private static final int STATIC_VAR = 10;
private String name;
private Shape shape = new Square(5, 10);
private String[] tags;
private int id;
@ToString(callSuper=true, includeFieldNames=true)
public static class Square extends Shape {
private final int width, height;
public Square(int width, int height) {
this.width = width;
this.height = height;
}
}
@ToString
public static class Shape {
private int color;
}
public static void main(String[] args) {
final ToStringExample example = new ToStringExample();
example.setId(1);
example.setName("abc");
example.setTags(new String[]{"a","b","c"});
final Shape shape = new Square(1,2);
example.setShape(shape);
System.out.println(example.toString());
}
}
1.@ToString
最簡單使用即可
打印結果如下:
ToStringExample(name=abc, shape=ToStringExample.Square(super=ToStringExample.Shape(color=0), width=1, height=2), tags=[a, b, c])
編譯后的代碼如下:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.test.lombok;
import java.util.Arrays;
public class ToStringExample {
private static final int STATIC_VAR = 10;
private String name;
private ToStringExample.Shape shape = new ToStringExample.Square(5, 10);
private String[] tags;
private int id;
public ToStringExample() {
}
public static void main(String[] args) {
ToStringExample example = new ToStringExample();
example.setId(1);
example.setName("abc");
example.setTags(new String[]{"a", "b", "c"});
ToStringExample.Shape shape = new ToStringExample.Square(1, 2);
example.setShape(shape);
System.out.println(example.toString());
}
public void setName(String name) {
this.name = name;
}
public void setShape(ToStringExample.Shape shape) {
this.shape = shape;
}
public void setTags(String[] tags) {
this.tags = tags;
}
public void setId(int id) {
this.id = id;
}
public String toString() {
return "ToStringExample(name=" + this.name + ", shape=" + this.shape + ", tags=" + Arrays.deepToString(this.tags) + ")";
}
public static class Shape {
private int color;
public Shape() {
}
public String toString() {
return "ToStringExample.Shape(color=" + this.color + ")";
}
}
public static class Square extends ToStringExample.Shape {
private final int width;
private final int height;
public Square(int width, int height) {
this.width = width;
this.height = height;
}
public String toString() {
return "ToStringExample.Square(super=" + super.toString() + ", width=" + this.width + ", height=" + this.height + ")";
}
}
}
@EqualsAndHashCode
equals()和hashCode()在Java中有着舉足輕重的基地作用,雖然通常很少關注。但是,這個必須不可省。不知道有幾個可以手寫出來的。
package com.test.lombok;
import lombok.EqualsAndHashCode;
/**
* Created by Ryan Miao on 1/18/18.
*/
@EqualsAndHashCode(exclude={"id", "shape"})
public class EqualsAndHashCodeExample {
private transient int transientVar = 10;
private String name;
private double score;
private ToStringExample.Shape shape = new Square(5, 10);
private String[] tags;
private int id;
public String getName() {
return this.name;
}
@EqualsAndHashCode(callSuper=true)
public static class Square extends ToStringExample.Shape {
private final int width, height;
public Square(int width, int height) {
this.width = width;
this.height = height;
}
}
public static void main(String[] args) {
EqualsAndHashCodeExample example = new EqualsAndHashCodeExample();
EqualsAndHashCodeExample example1 = new EqualsAndHashCodeExample();
boolean equals = example.equals(example1);
boolean b = example.canEqual(example);
int i = example.hashCode();
}
}
編譯后的結果為:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.test.lombok;
import com.test.lombok.ToStringExample.Shape;
import java.util.Arrays;
public class EqualsAndHashCodeExample {
private transient int transientVar = 10;
private String name;
private double score;
private Shape shape = new EqualsAndHashCodeExample.Square(5, 10);
private String[] tags;
private int id;
public EqualsAndHashCodeExample() {
}
public String getName() {
return this.name;
}
public static void main(String[] args) {
EqualsAndHashCodeExample example = new EqualsAndHashCodeExample();
EqualsAndHashCodeExample example1 = new EqualsAndHashCodeExample();
example.equals(example1);
boolean b = example.canEqual(example);
int i = example.hashCode();
}
public boolean equals(Object o) {
if (o == this) {
return true;
} else if (!(o instanceof EqualsAndHashCodeExample)) {
return false;
} else {
EqualsAndHashCodeExample other = (EqualsAndHashCodeExample)o;
if (!other.canEqual(this)) {
return false;
} else {
label31: {
Object this$name = this.getName();
Object other$name = other.getName();
if (this$name == null) {
if (other$name == null) {
break label31;
}
} else if (this$name.equals(other$name)) {
break label31;
}
return false;
}
if (Double.compare(this.score, other.score) != 0) {
return false;
} else {
return Arrays.deepEquals(this.tags, other.tags);
}
}
}
}
protected boolean canEqual(Object other) {
return other instanceof EqualsAndHashCodeExample;
}
public int hashCode() {
int PRIME = true;
int result = 1;
Object $name = this.getName();
int result = result * 59 + ($name == null ? 43 : $name.hashCode());
long $score = Double.doubleToLongBits(this.score);
result = result * 59 + (int)($score >>> 32 ^ $score);
result = result * 59 + Arrays.deepHashCode(this.tags);
return result;
}
public static class Square extends Shape {
private final int width;
private final int height;
public Square(int width, int height) {
this.width = width;
this.height = height;
}
public boolean equals(Object o) {
if (o == this) {
return true;
} else if (!(o instanceof EqualsAndHashCodeExample.Square)) {
return false;
} else {
EqualsAndHashCodeExample.Square other = (EqualsAndHashCodeExample.Square)o;
if (!other.canEqual(this)) {
return false;
} else if (!super.equals(o)) {
return false;
} else if (this.width != other.width) {
return false;
} else {
return this.height == other.height;
}
}
}
protected boolean canEqual(Object other) {
return other instanceof EqualsAndHashCodeExample.Square;
}
public int hashCode() {
int PRIME = true;
int result = super.hashCode();
result = result * 59 + this.width;
result = result * 59 + this.height;
return result;
}
}
}
構造函數@NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor
Java中class的一切起源於構造器。大家最喜歡的還是構造函數創建對象。這里有一點比較坑的是無參構造函數。當你自己添加一個帶有參數的構造函數后,無參構造函數則別隱藏。通常也沒啥問題,但當你使用jackson反序列化對象的時候就被惡心到了。jackson通過無參構造函數創建對象。因此,當你考慮這個class會用來序列化為json的時候,即必須手動添加一個無參數構造函數。
@NoArgsConstructor
當你想要創建一個valueobject,DDD中的值對象,要求實現Immutable,那么無參數構造器就不合適了。@NoArgsConstructor
會生成一個空的構造器。如果你設置了final field,那么編譯會報錯。如果你強制執行創建無參數構造器。即,@NoArgsConstructor(force = true)
,那么final的field會初始化為0
/false
/null
。通常適合與@Data
集成。
@NoArgsConstructor
public static class NoArgsExample {
@NonNull private String field;
}
NonNull
被忽略了
最終生成代碼如下:
public static class NoArgsExample {
@NonNull
private String field;
public NoArgsExample() {
}
}
對於final的字段,我認為我不會用空構造器來做這件事。所以,感覺這個參數force=true
不要也罷,雞肋。
@RequiredArgsConstructor
一個class可以有很多屬性,但你可能只關心其中的幾個字段,那么可以使用@RequiredArgsConstructor
。@NonNull
將標注這個字段不應為null,初始化的時候會檢查是否為空,否則拋出NullPointException
。在上面的無參構造函數中被忽略了。那么,對於關注的字段標注@NonNull
, @RequiredArgsConstructor
則會生成帶有這些字段的構造器。
@RequiredArgsConstructor
public class RequiredArgsExample {
@NonNull private String field;
private Date date;
private Integer integer;
private int i;
private boolean b;
private Boolean aBoolean;
}
最終生成結果:
public class RequiredArgsExample {
@NonNull
private String field;
private Date date;
private Integer integer;
private int i;
private boolean b;
private Boolean aBoolean;
public RequiredArgsExample(@NonNull String field) {
if (field == null) {
throw new NullPointerException("field");
} else {
this.field = field;
}
}
}
只有@NonNull
會生成構造器。其他默認,Java的class初始化默認為null.false,0.
lombok提供了另一種初始化做法,靜態初始化。即私有構造器,使用靜態方法創建對象。這種做法看起來簡單,但通常用的不多。因為靜態初始化的東西很難mock,對測試不夠友好。
@RequiredArgsConstructor(staticName = "of")
public static class RequiredArgsStaticExample {
@NonNull private String field;
private Date date;
private Integer integer;
private int i;
private boolean b;
private Boolean aBoolean;
}
最終生成代碼如下:
public class ConstructorExample<T> {
private int x;
private int y;
@NonNull
private T description;
private ConstructorExample(@NonNull T description) {
if (description == null) {
throw new NullPointerException("description");
} else {
this.description = description;
}
}
public static <T> ConstructorExample<T> of(@NonNull T description) {
return new ConstructorExample(description);
}
}
@AllArgsConstructor
想要初始化所有字段。
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public class ConstructorExample<T> {
private int x, y;
@NonNull
private T description;
}
最終生成代碼如下:
public class ConstructorExample<T> {
private int x;
private int y;
@NonNull
private T description;
protected ConstructorExample(int x, int y, @NonNull T description) {
if (description == null) {
throw new NullPointerException("description");
} else {
this.x = x;
this.y = y;
this.description = description;
}
}
}
必用項@Data
@Data
是一個集合體。包含Getter
,Setter
,RequiredArgsConstructor
,ToString
,EqualsAndHashCode
不可變對象valueobject @Value
這個看起來很美好,就是可以幫忙生成一個不可變對象。對於所有的字段都將生成final的。但我感覺有點失控。注解的優勢應該是所見即所得,可以通過字面量來傳遞消息。而@Value
字段給字段加final會讓人困惑,因為這更改了我們的定義。當我想聲明一個Immutable對象的時候,我會顯示的給字段加一個限定final。
同@Data
, @Value
是一個集合體。包含Getter
,AllArgsConstructor
,ToString
,EqualsAndHashCode
。
/**
* Created by Ryan Miao on 1/18/18.
*/
@Value
public class Room {
@NonNull
private String id;
private String name;
private boolean active;
private Date createTime;
}
編譯后
public final class Room {
@NonNull
private final String id;
private final String name;
private final boolean active;
private final Date createTime;
public Room(@NonNull String id, String name, boolean active, Date createTime) {
if (id == null) {
throw new NullPointerException("id");
} else {
this.id = id;
this.name = name;
this.active = active;
this.createTime = createTime;
}
}
@NonNull
public String getId() {
return this.id;
}
public String getName() {
return this.name;
}
public boolean isActive() {
return this.active;
}
public Date getCreateTime() {
return this.createTime;
}
public boolean equals(Object o) {
if (o == this) {
return true;
} else if (!(o instanceof Room)) {
return false;
} else {
Room other = (Room)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;
}
label41: {
Object this$name = this.getName();
Object other$name = other.getName();
if (this$name == null) {
if (other$name == null) {
break label41;
}
} else if (this$name.equals(other$name)) {
break label41;
}
return false;
}
if (this.isActive() != other.isActive()) {
return false;
} else {
Object this$createTime = this.getCreateTime();
Object other$createTime = other.getCreateTime();
if (this$createTime == null) {
if (other$createTime != null) {
return false;
}
} else if (!this$createTime.equals(other$createTime)) {
return false;
}
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.isActive() ? 79 : 97);
Object $createTime = this.getCreateTime();
result = result * 59 + ($createTime == null ? 43 : $createTime.hashCode());
return result;
}
public String toString() {
return "Room(id=" + this.getId() + ", name=" + this.getName() + ", active=" + this.isActive() + ", createTime=" + this.getCreateTime() + ")";
}
}
最喜歡的項 @Builder
對於喜歡builder模式的人來說,聲明式簡化對象創建流程讓一切看得美好。但是,手動復制字段,手動創建方法很讓人不喜。@Builder
解決了剛需。
/**
* Created by Ryan Miao on 1/18/18.
*/
@Data
@Builder(toBuilder = true)
public class Room {
@NonNull
private String id;
private String name;
private boolean active;
private Date createTime;
@Singular
private Set<String> occupations;
public static void main(String[] args) {
Room room = Room.builder().active(true)
.name("name")
.id("id")
.createTime(new Date())
.occupation("1")
.occupation("2")
.build();
Assert.assertEquals(2, room.getOccupations().size());
}
}
這才是我們想要的建造者。對應生成的代碼為:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.test.lombok;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.Set;
import lombok.NonNull;
import org.junit.Assert;
public class Room {
@NonNull
private String id;
private String name;
private boolean active;
private Date createTime;
private Set<String> occupations;
public static void main(String[] args) {
Room room = builder().active(true).name("name").id("id").createTime(new Date()).occupation("1").occupation("2").build();
Assert.assertEquals(2L, (long)room.getOccupations().size());
}
Room(@NonNull String id, String name, boolean active, Date createTime, Set<String> occupations) {
if (id == null) {
throw new NullPointerException("id");
} else {
this.id = id;
this.name = name;
this.active = active;
this.createTime = createTime;
this.occupations = occupations;
}
}
public static Room.RoomBuilder builder() {
return new Room.RoomBuilder();
}
public Room.RoomBuilder toBuilder() {
return (new Room.RoomBuilder()).id(this.id).name(this.name).active(this.active).createTime(this.createTime).occupations(this.occupations);
}
@NonNull
public String getId() {
return this.id;
}
public String getName() {
return this.name;
}
public boolean isActive() {
return this.active;
}
public Date getCreateTime() {
return this.createTime;
}
public Set<String> getOccupations() {
return this.occupations;
}
public void setId(@NonNull String id) {
if (id == null) {
throw new NullPointerException("id");
} else {
this.id = id;
}
}
public void setName(String name) {
this.name = name;
}
public void setActive(boolean active) {
this.active = active;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public void setOccupations(Set<String> occupations) {
this.occupations = occupations;
}
public boolean equals(Object o) {
if (o == this) {
return true;
} else if (!(o instanceof Room)) {
return false;
} else {
Room other = (Room)o;
if (!other.canEqual(this)) {
return false;
} else {
label63: {
Object this$id = this.getId();
Object other$id = other.getId();
if (this$id == null) {
if (other$id == null) {
break label63;
}
} else if (this$id.equals(other$id)) {
break label63;
}
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.isActive() != other.isActive()) {
return false;
} else {
Object this$createTime = this.getCreateTime();
Object other$createTime = other.getCreateTime();
if (this$createTime == null) {
if (other$createTime != null) {
return false;
}
} else if (!this$createTime.equals(other$createTime)) {
return false;
}
Object this$occupations = this.getOccupations();
Object other$occupations = other.getOccupations();
if (this$occupations == null) {
if (other$occupations != null) {
return false;
}
} else if (!this$occupations.equals(other$occupations)) {
return false;
}
return true;
}
}
}
}
protected boolean canEqual(Object other) {
return other instanceof Room;
}
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.isActive() ? 79 : 97);
Object $createTime = this.getCreateTime();
result = result * 59 + ($createTime == null ? 43 : $createTime.hashCode());
Object $occupations = this.getOccupations();
result = result * 59 + ($occupations == null ? 43 : $occupations.hashCode());
return result;
}
public String toString() {
return "Room(id=" + this.getId() + ", name=" + this.getName() + ", active=" + this.isActive() + ", createTime=" + this.getCreateTime() + ", occupations=" + this.getOccupations() + ")";
}
public static class RoomBuilder {
private String id;
private String name;
private boolean active;
private Date createTime;
private ArrayList<String> occupations;
RoomBuilder() {
}
public Room.RoomBuilder id(String id) {
this.id = id;
return this;
}
public Room.RoomBuilder name(String name) {
this.name = name;
return this;
}
public Room.RoomBuilder active(boolean active) {
this.active = active;
return this;
}
public Room.RoomBuilder createTime(Date createTime) {
this.createTime = createTime;
return this;
}
public Room.RoomBuilder occupation(String occupation) {
if (this.occupations == null) {
this.occupations = new ArrayList();
}
this.occupations.add(occupation);
return this;
}
public Room.RoomBuilder occupations(Collection<? extends String> occupations) {
if (this.occupations == null) {
this.occupations = new ArrayList();
}
this.occupations.addAll(occupations);
return this;
}
public Room.RoomBuilder clearOccupations() {
if (this.occupations != null) {
this.occupations.clear();
}
return this;
}
public Room build() {
Set occupations;
switch(this.occupations == null ? 0 : this.occupations.size()) {
case 0:
occupations = Collections.emptySet();
break;
case 1:
occupations = Collections.singleton(this.occupations.get(0));
break;
default:
Set<String> occupations = new LinkedHashSet(this.occupations.size() < 1073741824 ? 1 + this.occupations.size() + (this.occupations.size() - 3) / 3 : 2147483647);
occupations.addAll(this.occupations);
occupations = Collections.unmodifiableSet(occupations);
}
return new Room(this.id, this.name, this.active, this.createTime, occupations);
}
public String toString() {
return "Room.RoomBuilder(id=" + this.id + ", name=" + this.name + ", active=" + this.active + ", createTime=" + this.createTime + ", occupations=" + this.occupations + ")";
}
}
}
總結
lombok還提供了其他幾個注解,以及還有好多內置的參數沒有講解。但是,根據2-8原理,我們根本不需要。上面這幾個足夠了。更多的注解只會增加理解閱讀難度。