1、Lombok簡介
Lombok是一個插件,其主要用途是提供了簡單的注解的形式來幫助我們簡化消除一些必須有但顯得很臃腫的 java 代碼,提高編碼效率,使代碼更簡潔。
Lombok能以簡單的注解形式來簡化java代碼,提高開發人員的開發效率。例如開發中經常需要寫的javabean,都需要花時間去添加相應的getter/setter,也許還要去寫構造器、equals等方法,而且需要維護,當屬性多時會出現大量的getter/setter方法,這些顯得很冗長也沒有太多技術含量,一旦修改屬性,就容易出現忘記修改對應方法的失誤。
Lombok能通過注解的方式,在編譯時自動為屬性生成構造器、getter/setter、equals、hashcode、toString方法。
2、Lombok插件安裝
想要使用 Lombok,必須要安裝一個 Lombok 插件,不然的話,ide 編輯器會無法識別 Lombok 的注解,報找不到方法的異常。
以 idea 為例,直接在工具中搜索 Lombok 插件,然后安裝它。安裝完成后重啟 idea 開發工具即可。
接下來,我們需要在使用 Lombok 的項目中引入 Lombok 的 jar 包。Maven 的 pom.xml 中引入以下配置:
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.10</version> <scope>provided</scope> </dependency>
3、Lombok注解
3.1、變量相關
3.1.1、val和var
lombok幫助java低級版本擁有jdk10的特性(在lombok 0.10引入)。
使用var作為任何局部變量聲明的類型(即使在for語句中),該類型將從初始化表達式中推斷出來(該類型推斷中不涉及對變量的任何進一步賦值)。例如:
- var x=10.0; 將推斷double;
- var y=new arraylist(); 將推斷Arraylist;
注意:
- 對於復合類型,推斷出最常見的父類,而不是接口。例如,bool ? new HashSet() : new ArrayList()是一個帶有復合類型的表達式.存在同樣的父類:AbstractCollection,也實現了同樣的接口:Serializable。最終推斷的類型將是AbstractCollection,因為它是一個類,而Serializable是接口。
- 在不明確的情況下,例如當初始化表達式是null,將推斷為java.lang.Object
- 這是一個注釋類型,因為var x=10;將被解語法糖為@var int x=10;
- 注意:var 和 val 具有完全相同的語義,只是val變成@val
但是推薦使用val,因為idea具有val自帶的postfix template:
//使用示例 public String example() { val example = new ArrayList<String>(); example.add("Hello, World!"); val foo = example.get(0); return foo.toLowerCase(); }
3.1.2、NonNull
在方法參數上:Lombok將在方法/構造函數體的開頭插入一個空檢查,並將參數的名稱作為消息引發一個NullPointerException。
在字段上:任何為該字段賦值的生成方法也將生成這些空檢查。
例如:
public class DataExample { @NonNull private String name; }
編譯后的class為:
public class DataExample { public DataExample(@NonNull String name) { if (name == null) throw new NullPointerException("name is marked non-null but is null"); this.name = name; } public void setName(@NonNull String name) { if (name == null) throw new NullPointerException("name is marked non-null but is null"); this.name = name; } }
3.2、實體類相關
3.2.1、@Getter、@Setter
取代實體類中的get和set方法:可以在任何字段上使用@Getter或@Setter,lombok會自動生成默認的getter / setter;如果在類上使用,則所有字段生成getter / setter。
AccessLevel:可以通過AccessLevel重寫訪問級別。
關於字段注釋(lombok v1.12.0中的新功能):將字段上的javadoc復制到生成的getter和setter。
關於方法命名:
getter:默認情況下方法名為:get+字段名以駝峰連接;如果是boolean類型則:is+字段名以駝峰連接。(Boolean類型是get開頭)。不建議使用is開頭的字段命名,會產生混淆:
setter:setter不受boolean影響:set+字段名以駝峰連接。
例如:
public class DataExample { @Setter(AccessLevel.PROTECTED) private String name; @Getter @Setter boolean flag; @Getter @Setter Boolean flagObj; }
編譯后的class為:
public class DataExample {private String name; boolean flag; Boolean flagObj; protected void setName(String name) { this.name = name; } public boolean isFlag() { return this.flag; } public void setFlag(boolean flag) { this.flag = flag; } public Boolean getFlagObj() { return this.flagObj; } public void setFlagObj(Boolean flagObj) { this.flagObj = flagObj; } }
特殊事項:
- @Getter可用於枚舉而@Setter不能
- 來自流行庫的注釋檢查,例如javax.annotation.Nonnull,如果存在於字段上,導致生成的setter中會進行顯式的空檢查。
- 當與@Accessors注解一起使用會產生影響
3.2.2、@Accessors
使用在類或字段上,目的是修改getter和setter方法的內容(在lombok v0.11.0中作為實驗特征引入)。
功能:
- fluent:如果為true,那么getter 和setter 生成的方法名沒有前綴。此外,除非指定,否則chain將為true。
- chain:如果為true,則生成的setter返回this而不是void。默認值:false
- prefix:如果存在,則字段必須以任何這些前綴為前綴。每個字段名稱依次與列表中的每個前綴進行比較,如果找到匹配項,則會剝離前綴以創建字段的基本名稱。在列表中包含一個空字符串是合法的它將始終匹配。對於字母的字符,前綴后面的字符不能是小寫字母,即以p為前綴也不會匹配pepper,但是pEpper會被匹配上(並且意味着該字段的基本名稱epper)。++如果提供了前綴列表並且字段不以其中一個字段開頭,則lombok將完全跳過該字段,並將生成警告++
示例如下:
@Getter @Setter public class AccessorsExample { @Accessors(fluent = true) private int age = 10; @Accessors(prefix = "f") private String fName; @Accessors(chain = true) private String chainName; }
編譯后的class為:
public class AccessorsExample { private int age = 10; private String fName; private String chainName; public int age() { return this.age; } public AccessorsExample age(int age) { this.age = age; return this; } public String getName() { return this.fName; } public void setName(String fName) { this.fName = fName; } public AccessorsExample setChainName(String chainName) { this.chainName = chainName; return this; } public String getChainName() { return this.chainName; } }
3.2.3、@AllArgsConstructor、RequiredArgsConstructor、@NoArgsConstructor
分別生成全參構造方法,帶參構造,無參構造。
@NoArgsConstructor : 生成一個沒有參數的構造器。
@AllArgsConstructor : 生成一個包含所有參數的構造器
@RequiredArgsConstructor : 生成一個包含 "特定參數" 的構造器,特定參數指的是那些有加上 final 修飾詞的變量們或者加了@NonNull注解的參數。如果指定staticName = "of"參數,還會生成一個返回類對象的靜態工廠方法。
如下例:
@RequiredArgsConstructor(staticName = "of") @AllArgsConstructor(access = AccessLevel.PROTECTED) public class ConstructorExample<T> { private int x, y; @NonNull private T description; private final String finalString; @NoArgsConstructor public static class NoArgsExample { @NonNull private String field; } }
經過編譯后,class文件反編譯,實際的類文件等同於:
public class ConstructorExample<T> { private int x; private int y; @NonNull private T description; private final String finalString; private ConstructorExample(@NonNull T description, String finalString) { if (description == null) throw new NullPointerException("description is marked non-null but is null"); this.description = description; this.finalString = finalString; } public static <T> ConstructorExample<T> of(@NonNull T description, String finalString) { return new ConstructorExample<>(description, finalString); } protected ConstructorExample(int x, int y, @NonNull T description, String finalString) { if (description == null) throw new NullPointerException("description is marked non-null but is null"); this.x = x; this.y = y; this.description = description; this.finalString = finalString; } public static class NoArgsExample { @NonNull private String field; } }
這里注意一個 Java 的小坑,當我們沒有指定構造器時,Java 編譯器會幫我們自動生成一個沒有任何參數的構造器給該類,但是如果我們自己寫了構造器之后,Java 就不會自動幫我們補上那個無參數的構造器了,然而很多地方(像是 Spring Data JPA),會需要每個類都一定要有一個無參數的構造器,所以你在加上 @AllArgsConstructor
時,一定要補上 @NoArgsConstrcutor
,不然會有各種坑等着你。
3.2.4、@ToString
類使用@ToString注解,Lombok會生成一個toString()方法,默認情況下,會輸出類名、所有屬性(會按照屬性定義順序),用逗號來分割。通過將includeFieldNames
參數設為true,就能明確的輸出toString()屬性。這一點是不是有點繞口,通過代碼來看會更清晰些。
使用Lombok的示例:
@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; } } }
經過編譯后,class文件反編譯,實際的類文件等同於:
public class ToStringExample { private int id; private static final int STATIC_VAR = 10; private String name; private Shape shape = new Square(5, 10); private String[] tags; public String toString() { return "ToStringExample(name=" + getName() + ", shape=" + this.shape + ", tags=" + Arrays.deepToString((Object[])this.tags) + ")"; } 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 String toString() { return "ToStringExample.Square(super=" + super.toString() + ", width=" + this.width + ", height=" + this.height + ")"; } } }
3.2.5、@EqualsAndHashCode
默認情況下,會使用所有非靜態(non-static)和非瞬態(non-transient)屬性來生成equals/canEqual和hasCode,也能通過exclude注解來排除一些屬性。
使用Lombok的示例:
@EqualsAndHashCode(exclude={"id", "shape"}) public class EqualsAndHashCodeExample { private transient int transientVar = 10; private String name; private double score; private Shape shape = new Square(5, 10); private String[] tags; private int id; @EqualsAndHashCode(callSuper=true) public static class Square extends Shape { private final int width, height; public Square(int width, int height) { this.width = width; this.height = height; } @Override public com.sun.javafx.geom.Shape impl_configShape() { return null; } } }
經過編譯后,class文件反編譯,實際的類文件等同於:
public class EqualsAndHashCodeExample { private transient int transientVar = 10; private int id; private String name; private double score; private Shape shape = new Square(5, 10); private String[] tags; public int hashCode() { int PRIME = 59; result = 1; Object $name = getName(); result = result * 59 + (($name == null) ? 43 : $name.hashCode()); long $score = Double.doubleToLongBits(this.score); result = result * 59 + (int)($score >>> 32L ^ $score); return result * 59 + Arrays.deepHashCode((Object[])this.tags); } protected boolean canEqual(Object other) { return other instanceof EqualsAndHashCodeExample; } public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof EqualsAndHashCodeExample)) return false; EqualsAndHashCodeExample other = (EqualsAndHashCodeExample)o; if (!other.canEqual(this)) return false; Object this$name = getName(), other$name = other.getName(); return ((this$name == null) ? (other$name != null) : !this$name.equals(other$name)) ?
false : ((Double.compare(this.score, other.score) != 0) ? false : (!!Arrays.deepEquals((Object[])this.tags, (Object[])other.tags))); } public static class Square extends Shape { private final int width; private final int height; public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof Square)) return false; Square other = (Square)o; return !other.canEqual(this) ? false : (!super.equals(o) ?
false : ((this.width != other.width) ? false : (!(this.height != other.height)))); } protected boolean canEqual(Object other) { return other instanceof Square; } public int hashCode() { int PRIME = 59; result = super.hashCode(); result = result * 59 + this.width; return result * 59 + this.height; } public Square(int width, int height) { this.width = width; this.height = height; } } }
3.2.6、@Data
作用在類上,相當於集成以下注解
- @Getter
- @Setter
- @RequiredArgsConstructor
- @ToString
- @EqualsAndHashCode
其獨有屬性配置:
- staticConstructor 靜態方法名:@Data(staticConstructor=“of”)通過新的方式來創建實例:Foo.of(5)
Lombok示例如下:
@Data public class DataExample { private final String name; @Setter(AccessLevel.PACKAGE) private int age; private double score; private String[] tags; @ToString(includeFieldNames=true) @Data(staticConstructor="of") public static class Exercise<T> { private final String name; private final T value; } }
經過編譯后,class文件反編譯,實際的類文件等同於:
public class DataExample { private final String name; private int age; private double score; private String[] tags; public DataExample(String name) { this.name = name; } public String getName() { return this.name; } void setAge(int age) { this.age = age; } public int getAge() { return this.age; } public void setScore(double score) { this.score = score; } public double getScore() { return this.score; } public String[] getTags() { return this.tags; } public void setTags(String[] tags) { this.tags = tags; } @Override public String toString() { return "DataExample(" + this.getName() + ", " + this.getAge() + ", " + this.getScore() + ", " + Arrays.deepToString(this.getTags()) + ")"; } protected boolean canEqual(Object other) { return other instanceof DataExample; } @Override public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof DataExample)) return false; DataExample other = (DataExample) o; if (!other.canEqual((Object)this)) return false; if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false; if (this.getAge() != other.getAge()) return false; if (Double.compare(this.getScore(), other.getScore()) != 0) return false; if (!Arrays.deepEquals(this.getTags(), other.getTags())) return false; return true; } @Override public int hashCode() { final int PRIME = 59; int result = 1; final long temp1 = Double.doubleToLongBits(this.getScore()); result = (result*PRIME) + (this.getName() == null ? 43 : this.getName().hashCode()); result = (result*PRIME) + this.getAge(); result = (result*PRIME) + (int)(temp1 ^ (temp1 >>> 32)); result = (result*PRIME) + Arrays.deepHashCode(this.getTags()); return result; } public static class Exercise<T> { private final String name; private final T value; private Exercise(String name, T value) { this.name = name; this.value = value; } public static <T> Exercise<T> of(String name, T value) { return new Exercise<T>(name, value); } public String getName() { return this.name; } public T getValue() { return this.value; } @Override public String toString() { return "Exercise(name=" + this.getName() + ", value=" + this.getValue() + ")"; } protected boolean canEqual(Object other) { return other instanceof Exercise; } @Override public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof Exercise)) return false; Exercise<?> other = (Exercise<?>) o; if (!other.canEqual((Object)this)) return false; if (this.getName() == null ? other.getValue() != null : !this.getName().equals(other.getName())) return false; if (this.getValue() == null ? other.getValue() != null : !this.getValue().equals(other.getValue())) return false; return true; } @Override public int hashCode() { final int PRIME = 59; int result = 1; result = (result*PRIME) + (this.getName() == null ? 43 : this.getName().hashCode()); result = (result*PRIME) + (this.getValue() == null ? 43 : this.getValue().hashCode()); return result; } } }
通常 @Data 會加在一個值可以被更新的對象上,像是日常使用的 DTO 們、或是 JPA 裡的 Entity 們,就很適合加上 @Data 注解,也就是 @Data for mutable class。
3.2.7、@Value
也是整合包,但是他會把所有的變量都設成 final 的,其他的就跟 @Data 一樣,等於同時加了以下注解:@ToString,@EqualsAndHashCode,@AllArgsConstructor,@FieldDefaults,@Getter。
所有字段由private和final修飾,不會產生setter方法。類本身也是由final修飾。
創建Lombok示例如下:
@Value public class ValueExample { String name; @NonFinal int age; double score; protected String[] tags; @ToString(includeFieldNames=true) @Value(staticConstructor="of") public static class Exercise<T> { String name; T value; } }
經過編譯后,class文件反編譯,實際的類文件等同於:
public final class ValueExample { private final String name; private int age; private final double score; protected final String[] tags; public ValueExample(String name, int age, double score, String[] tags) { this.name = name; this.age = age; this.score = score; this.tags = tags; } public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof ValueExample)) return false; ValueExample other = (ValueExample)o; Object this$name = getName(), other$name = other.getName(); return ((this$name == null) ? (other$name != null) : !this$name.equals(other$name)) ? false : ((getAge() != other.getAge()) ? false : ((Double.compare(getScore(), other.getScore()) != 0) ? false : (!!Arrays.deepEquals((Object[])getTags(), (Object[])other.getTags())))); } public int hashCode() { int PRIME = 59; result = 1; //不知道為什么反編譯class會是這樣的寫法 Object $name = getName(); result = result * 59 + (($name == null) ? 43 : $name.hashCode()); result = result * 59 + getAge(); long $score = Double.doubleToLongBits(getScore()); result = result * 59 + (int)($score >>> 32L ^ $score); return result * 59 + Arrays.deepHashCode((Object[])getTags()); } public String toString() { return "ValueExample(name=" + getName() + ", age=" + getAge() + ", score=" + getScore() + ", tags=" + Arrays.deepToString((Object[])getTags()) + ")"; } public String getName() { return this.name; } public int getAge() { return this.age; } public double getScore() { return this.score; } public String[] getTags() { return this.tags; } public static final class Exercise<T> { private final String name; private final T value; public String toString() { return "ValueExample.Exercise(name=" + getName() + ", value=" + getValue() + ")"; } private Exercise(String name, T value) { this.name = name; this.value = value; } public static <T> Exercise<T> of(String name, T value) { return new Exercise<>(name, value); } public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof Exercise)) return false; Exercise<?> other = (Exercise)o; Object this$name = getName(), other$name = other.getName(); if ((this$name == null) ? (other$name != null) : !this$name.equals(other$name)) return false; Object this$value = getValue(), other$value = other.getValue(); return !((this$value == null) ? (other$value != null) : !this$value.equals(other$value)); } public int hashCode() { int PRIME = 59; result = 1; //不知道為什么反編譯class會是這樣的寫法 Object $name = getName(); result = result * 59 + (($name == null) ? 43 : $name.hashCode()); Object $value = getValue(); return result * 59 + (($value == null) ? 43 : $value.hashCode()); } public String getName() { return this.name; } public T getValue() { return this.value; } } }
3.2.8、@Builder、@Singular
@Builder:通過內部類和一個全參構造器來實現構造者模式。
@Singular:自動生成構造者模式代碼,Singular是針對集合屬性的特殊處理。@Singular對集合進行操作,最終會生成不可變集合
不可變集合:底層是可變集合,但是修改時會拋出異常。
如示例:
@Builder public class SingularExample<T extends Number> { private @Singular Set<String> occupations; private @Singular("axis") ImmutableList<String> axes; private @Singular SortedMap<Integer, T> elves; private @Singular Collection<?> minutiae; }
經過編譯后,class文件反編譯,實際的類文件等同於:
public class SingularExample<T extends Number> { private Set<String> occupations; private ImmutableList<String> axes; private SortedMap<Integer, T> elves; private Collection<?> minutiae; SingularExample(Set<String> occupations, ImmutableList<String> axes, SortedMap<Integer, T> elves, Collection<?> minutiae) { this.occupations = occupations; this.axes = axes; this.elves = elves; this.minutiae = minutiae; } public static <T extends Number> SingularExampleBuilder<T> builder() { return new SingularExampleBuilder<>(); } public static class SingularExampleBuilder<T extends Number> { private ArrayList<String> occupations; private ImmutableList.Builder<String> axes; private ArrayList<Integer> elves$key; private ArrayList<T> elves$value; private ArrayList<Object> minutiae; public SingularExampleBuilder<T> occupation(String occupation) { if (this.occupations == null) this.occupations = new ArrayList<>(); this.occupations.add(occupation); return this; } public SingularExampleBuilder<T> occupations(Collection<? extends String> occupations) { if (this.occupations == null) this.occupations = new ArrayList<>(); this.occupations.addAll(occupations); return this; } public SingularExampleBuilder<T> clearOccupations() { if (this.occupations != null) this.occupations.clear(); return this; } public SingularExampleBuilder<T> axis(String axis) { if (this.axes == null) this.axes = ImmutableList.builder(); this.axes.add(axis); return this; } public SingularExampleBuilder<T> axes(Iterable<? extends String> axes) { if (this.axes == null) this.axes = ImmutableList.builder(); this.axes.addAll(axes); return this; } public SingularExampleBuilder<T> clearAxes() { this.axes = null; return this; } public SingularExampleBuilder<T> elf(Integer elfKey, T elfValue) { if (this.elves$key == null) { this.elves$key = new ArrayList<>(); this.elves$value = new ArrayList<>(); } this.elves$key.add(elfKey); this.elves$value.add(elfValue); return this; } public SingularExampleBuilder<T> elves(Map<? extends Integer, ? extends T> elves) { if (this.elves$key == null) { this.elves$key = new ArrayList<>(); this.elves$value = new ArrayList<>(); } for (Map.Entry<? extends Integer, ? extends T> $lombokEntry : elves.entrySet()) { this.elves$key.add($lombokEntry.getKey()); this.elves$value.add($lombokEntry.getValue()); } return this; } public SingularExampleBuilder<T> clearElves() { if (this.elves$key != null) { this.elves$key.clear(); this.elves$value.clear(); } return this; } public SingularExampleBuilder<T> minutia(Object minutia) { if (this.minutiae == null) this.minutiae = new ArrayList(); this.minutiae.add(minutia); return this; } public SingularExampleBuilder<T> minutiae(Collection<?> minutiae) { if (this.minutiae == null) this.minutiae = new ArrayList(); this.minutiae.addAll(minutiae); return this; } public SingularExampleBuilder<T> clearMinutiae() { if (this.minutiae != null) this.minutiae.clear(); return this; } public SingularExample<T> build() { Set<String> occupations; switch ((this.occupations == null) ? 0 : this.occupations.size()) { case false: occupations = Collections.emptySet(); break; case true: occupations = Collections.singleton(this.occupations.get(0)); break; default: occupations = new LinkedHashSet<>((this.occupations.size() < 1073741824) ? (1 + this.occupations.size() + (this.occupations.size() - 3) / 3) : Integer.MAX_VALUE); occupations.addAll(this.occupations); occupations = Collections.unmodifiableSet(occupations); break; } ImmutableList<String> axes = (this.axes == null) ? ImmutableList.of() : this.axes.build(); SortedMap<Integer, T> elves = new TreeMap<>(); if (this.elves$key != null) for (int $i = 0; $i < ((this.elves$key == null) ? 0 : this.elves$key.size()); ) { elves.put(this.elves$key.get($i), this.elves$value.get($i)); $i++; } elves = Collections.unmodifiableSortedMap(elves); switch ((this.minutiae == null) ? 0 : this.minutiae.size()) { case false: minutiae = Collections.emptyList(); return new SingularExample<>(occupations, axes, elves, minutiae); case true: minutiae = Collections.singletonList(this.minutiae.get(0)); return new SingularExample<>(occupations, axes, elves, minutiae); } Collection<Object> minutiae = Collections.unmodifiableList(new ArrayList(this.minutiae)); return new SingularExample<>(occupations, axes, elves, minutiae); } public String toString() { return "SingularExample.SingularExampleBuilder(occupations=" + this.occupations + ", axes=" + this.axes
+ ", elves$key=" + this.elves$key + ", elves$value=" + this.elves$value + ", minutiae=" + this.minutiae + ")"; } } }
注意:
- 雖然只要加上 @Builder 注解,我們就能夠用流式寫法快速設定對象的值,但是 setter 還是必須要寫不能省略的,因為 Spring 或是其他框架有很多地方都會用到對象的 getter/setter 對他們取值/賦值,所以通常是 @Data 和 @Builder 會一起用在同個類上,既方便我們流式寫代碼,也方便框架做事。
- 由於Builder會生成一個全參構造器,導致默認的無參構造器失效,所以類采用@Builder注解后無法new出來。完美的避免方式:加上@AllArgsConstructor、@NoArgsConstructor后就可以同時使用new和構造者方式實例化對象了。
@Builder @AllArgsConstructor @NoArgsConstructor
- 父類中的屬性子類繼承問題:簡單解決方案是在子類中也手動寫該屬性
3.2.9、@Getter(lazy=true)
可以替代經典的Double Check Lock樣板代碼,如下示例:
public class GetterLazyExample { @Getter(lazy = true) private final double[] cached = expensive(); private double[] expensive() { double[] result = new double[1000000]; for (int i = 0; i < result.length; i++) { result[i] = Math.asin(i); } return result; } } //經過編譯,相當於以下這個class public class GetterLazyExample { private final AtomicReference<Object> cached = new AtomicReference(); public double[] getCached() { Object value = this.cached.get(); if (value == null) synchronized (this.cached) { value = this.cached.get(); if (value == null) { double[] actualValue = expensive(); value = (actualValue == null) ? this.cached : actualValue; this.cached.set(value); } } return (value == this.cached) ? null : (double[])value; } private double[] expensive() { double[] result = new double[1000000]; for (int i = 0; i < result.length; i++) result[i] = Math.asin(i); return result; } }
3.3、工具類注解
3.3.1、@Cleanup
自動資源管理:自動調用關閉資源方法,默認是調用close()方法,在finally塊中,只有在給定資源不是null的情況下才會調用清理方法。
如果要清理的對象沒有close()方法,可以指定一個無參方法來進行自動清理。
@Cleanup("dispose") CoolBar bar = new CoolBar(parent, 0);
如Lombok示例:
public class CleanupExample { 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文件反編譯,實際的類文件等同於:
public 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(); } } } }
3.3.2、@Log、@Slf4j、@Log4j、@Log4j2、@XSlf4j、@CommonsLog
自動生成該類的 log 靜態常量,要打日志就可以直接打,不用再手動 new log 靜態常量了。lombok 提供@Log、@Slf4j、@Log4j、@Log4j2、@XSlf4j、@CommonsLog日志框架的變種注解,他們都是幫我們創建一個靜態常量 log,只是使用的庫不一樣而已。
SpringBoot默認支持的就是 slf4j + logback 的日志框架,所以也不用再多做啥設定,直接就可以用在 SpringBoot project上,log 系列注解最常用的就是 @Slf4j。
對應注解的log靜態常亮初始化如下:
@Log //對應的log語句如下 private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LogExample.class.getName()); @Log4j //對應的log語句如下 private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(LogExample.class); @Log4j2 private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class); @CommonsLog private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(LogExample.class); @XSlf4j private static final org.slf4j.ext.XLogger log = org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class); @Slf4j private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CleanupExample.class);
3.3.3、@SneakyThrows
Lombok示例如下:
public class SneakyThrowsExample implements Runnable { @SneakyThrows(UnsupportedEncodingException.class) public String utf8ToString(byte[] bytes) { return new String(bytes, "UTF-8"); } @SneakyThrows public void run() { throw new Throwable(); } }
經過編譯后,class文件反編譯,實際的類文件等同於:
public class SneakyThrowsExample implements Runnable { public String utf8ToString(byte[] bytes) { try { return new String(bytes, "UTF-8"); } catch (UnsupportedEncodingException e) { throw Lombok.sneakyThrow(e); } } public void run() { try { throw new Throwable(); } catch (Throwable t) { throw Lombok.sneakyThrow(t); } } }
3.3.4、@Synchronized
synchronized是線程安全中一個重要的關鍵字,它是一種同步鎖,主要用來保證在同一個時刻,只有一個線程可以執行某個方法或者某段代碼塊。一般使用synchronized去鎖住代碼塊,而不是方法,因為鎖住代碼塊效率更高。
@Synchronized是synchronized方法修飾符的更安全的變體。@synchronized 的作用是創建一個互斥鎖,保證此時沒有其它線程對self對象進行修改,保證代碼的安全性。也就是包裝這段代碼是原子性的,安全的。這個是objective-c的一個鎖定令牌,防止self對象在同一時間內被其他線程訪問,起到保護線程安全的作用。
與一樣synchronized,注釋只能在靜態方法和實例方法上使用。它的操作類似於synchronized關鍵字,但是它鎖定在不同的對象上。關鍵字鎖定在上this,但注釋鎖定在名為的字段上$ lock,該字段是私有的。如果該字段不存在,則會為您創建。如果對static方法進行注釋,則注釋將鎖定在名為的靜態字段上$ LOCK。
如果 $lock 或 $LOCK 是自動生成的,則字段將使用一個空Object[]數組進行初始化,而不僅僅是new Object()顯示該模式在實際使用中的大多數代碼片段。Lombok這樣做是因為新對象不可序列化,而0大小的數組則可序列化。因此,使用@Synchronized不會阻止您的對象被序列化。
@Synchronized類 中至少有一個方法意味着將有一個鎖字段,但是如果以后刪除所有這些方法,將不再有鎖字段。這意味着您預定的serialVersionUID更改。如果您打算通過Java的序列化機制長期存儲它們,我們建議您始終serialVersionUID向類中添加a 。如果這樣做,@Synchronized從方法中刪除所有注釋不會破壞序列化。
如Lombok示例如下:
public class SynchronizedExample{ private static final Object manualLock = new Object(); public static void main(String[] args) { hello("HJ"); } @Synchronized private static void hello(String print) { System.out.println("hello" + print); } @Synchronized public void print(){ System.out.println("print"); }; @Synchronized("manualLock") public void manualLock(){ System.out.println("manualLock"); }; }
經過編譯后,class文件反編譯,實際的類文件等同於:
public class SynchronizedExample { private static final Object manualLock = new Object(); public static void main(String[] args) { hello("HJ"); } private static final Object $LOCK = new Object[0]; private static void hello(String print) { synchronized ($LOCK) { System.out.println("hello" + print); } } private final Object $lock = new Object[0]; public void print() { synchronized (this.$lock) { System.out.println("print"); } } public void manualLock() { this.getClass(); synchronized (manualLock) { System.out.println("manualLock"); } } }
@Synchronized源碼如下:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Synchronized { /** * Optional: specify the name of a different field to lock on. It is a compile time error if this field * doesn't already exist (the fields are automatically generated only if you don't specify a specific name * @return Name of the field to lock on (blank = generate one). */ String value() default ""; //注解屬性:value是指自定義鎖的字段名。 }
4、Lombok原理解析
Lombok使用的過程中,只需要添加相應的注解,無需再為此寫任何代碼。但是自動生成的代碼到底是如何產生的呢?
核心之處就是對於注解的解析上。JDK5引入了注解的同時,也提供了兩種解析方式。
- 運行時解析:運行時能夠解析的注解,必須將@Retention設置為RUNTIME,這樣就可以通過反射拿到該注解。java.lang,reflect反射包中提供了一個接口AnnotatedElement,該接口定義了獲取注解信息的幾個方法,Class、Constructor、Field、Method、Package等都實現了該接口,對反射熟悉的朋友應該都會很熟悉這種解析方式。
- 編譯時解析
編譯時解析有兩種機制,分別簡單描述下:
- Annotation Processing Tool:apt自JDK5產生,JDK7已標記為過期,不推薦使用,JDK8中已徹底刪除,自JDK6開始,可以使用Pluggable Annotation Processing API來替換它,apt被替換主要有2點原因:api都在com.sun.mirror非標准包下;沒有集成到javac中,需要額外運行。
- Pluggable Annotation Processing API:JSR 269自JDK6加入,作為apt的替代方案,它解決了apt的兩個問題,javac在執行的時候會調用實現了該API的程序,這樣我們就可以對編譯器做一些增強,這時javac執行的過程如下:
Lombok本質上就是一個實現了“JSR 269 API”的程序。在使用javac的過程中,它產生作用的具體流程如下:
- javac對源代碼進行分析,生成了一棵抽象語法樹(AST)
- 運行過程中調用實現了“JSR 269 API”的Lombok程序
- 此時Lombok就對第一步驟得到的AST進行處理,找到@Data注解所在類對應的語法樹(AST),然后修改該語法樹(AST),增加getter和setter方法定義的相應樹節點
- javac使用修改后的抽象語法樹(AST)生成字節碼文件,即給class增加新的節點(代碼塊)
拜讀了Lombok源碼,對應注解的實現都在HandleXXX中,比如@Getter注解的實現時HandleGetter.handle()。還有一些其它類庫使用這種方式實現,比如Google Auto、Dagger等等。
附Lombok注解類型: