Lombok為啥這麼牛逼?SpringBoot和IDEA官方都要支持它
最近 IDEA 2020最後一個版本發佈瞭 ,已經內置瞭Lombok插件,SpringBoot 2.1.x之後的版本也在Starter中內置瞭Lombok依賴。為什麼他們都要支持Lombok呢?今天我來講講Lombok的使用,看看它有何神奇之處!
Lombok簡介
Lombok是一款Java代碼功能增強庫,在Github上已有9.8k+Star。它會自動集成到你的編輯器和構建工具中,從而使你的Java代碼更加生動有趣。通過Lombok的註解,你可以不用再寫getter、setter、equals等方法,Lombok將在編譯時為你自動生成。
Lombok集成
首先我們需要在IDEA中安裝好Lombok插件,如果你使用的是最新版IDEA 2020.3,則Lombok插件已經內置,無需安裝。
之後在項目的pom.xml文件中添加Lombok依賴,SpringBoot 2.1.x版本後無需指定Lombok版本,SpringBoot在 spring-boot-dependencies
中已經內置。
<!--lombok依賴--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>
Lombok使用
Lombok中有很多註解,這些註解使得我們可以更加方便的編寫Java代碼,下面介紹下這些註解的使用。
val
使用val註解可以取代任意類型作為局部變量,這樣我們就不用寫復雜的ArrayList和Map.Entry類型瞭,具體例子如下。
/** * Created by macro on 2020/12/16. */ public class ValExample { public static void example() { //val代替ArrayList<String>和String類型 val example = new ArrayList<String>(); example.add("Hello World!"); val foo = example.get(0); System.out.println(foo.toLowerCase()); } public static void example2() { //val代替Map.Entry<Integer,String>類型 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()); } } public static void main(String[] args) { example(); example2(); } }
當我們使用瞭val註解後,Lombok會從局部變量的初始化表達式推斷出具體類型,編譯後會生成如下代碼。
public class ValExample { public ValExample() { } public static void example() { ArrayList<String> example = new ArrayList(); example.add("Hello World!"); String foo = (String)example.get(0); System.out.println(foo.toLowerCase()); } public static void example2() { HashMap<Integer, String> map = new HashMap(); map.put(0, "zero"); map.put(5, "five"); Iterator var1 = map.entrySet().iterator(); while(var1.hasNext()) { Entry<Integer, String> entry = (Entry)var1.next(); System.out.printf("%d: %s\n", entry.getKey(), entry.getValue()); } } }
@NonNull
在方法上使用@NonNull註解可以做非空判斷,如果傳入空值的話會直接拋出NullPointerException。
/** * Created by macro on 2020/12/16. */ public class NonNullExample { private String name; public NonNullExample(@NonNull String name){ this.name = name; } public static void main(String[] args) { new NonNullExample("test"); //會拋出NullPointerException new NonNullExample(null); } }
編譯後會在構造器中添加非空判斷,具體代碼如下。
public class NonNullExample { private String name; public NonNullExample(@NonNull String name) { if (name == null) { throw new NullPointerException("name is marked non-null but is null"); } else { this.name = name; } } public static void main(String[] args) { new NonNullExample("test"); new NonNullExample((String)null); } }
@Cleanup
當我們在Java中使用資源時,不可避免地需要在使用後關閉資源。使用@Cleanup註解可以自動關閉資源。
/** * Created by macro on 2020/12/16. */ public class CleanupExample { public static void main(String[] args) throws IOException { String inStr = "Hello World!"; //使用輸入輸出流自動關閉,無需編寫try catch和調用close()方法 @Cleanup ByteArrayInputStream in = new ByteArrayInputStream(inStr.getBytes("UTF-8")); @Cleanup ByteArrayOutputStream out = new ByteArrayOutputStream(); byte[] b = new byte[1024]; while (true) { int r = in.read(b); if (r == -1) break; out.write(b, 0, r); } String outStr = out.toString("UTF-8"); System.out.println(outStr); } }
編譯後Lombok會生成如下代碼。
public class CleanupExample { public CleanupExample() { } public static void main(String[] args) throws IOException { String inStr = "Hello World!"; ByteArrayInputStream in = new ByteArrayInputStream(inStr.getBytes("UTF-8")); try { ByteArrayOutputStream out = new ByteArrayOutputStream(); try { byte[] b = new byte[1024]; while(true) { int r = in.read(b); if (r == -1) { String outStr = out.toString("UTF-8"); System.out.println(outStr); return; } out.write(b, 0, r); } } finally { if (Collections.singletonList(out).get(0) != null) { out.close(); } } } finally { if (Collections.singletonList(in).get(0) != null) { in.close(); } } } }
@Getter/@Setter
有瞭@Getter/@Setter註解,我們再也不用編寫getter/setter方法瞭。試想下之前即使我們使用IDEA自動生成getter/setter方法,如果類屬性的類型和名稱改瞭,又要重新生成getter/setter方法也是一件很麻煩的事情。
/** * Created by macro on 2020/12/17. */ public class GetterSetterExample { @Getter @Setter private String name; @Getter @Setter(AccessLevel.PROTECTED) private Integer age; public static void main(String[] args) { GetterSetterExample example = new GetterSetterExample(); example.setName("test"); example.setAge(20); System.out.printf("name:%s age:%d",example.getName(),example.getAge()); } }
編譯後Lombok會生成如下代碼。
public class GetterSetterExample { private String name; private Integer age; public GetterSetterExample() { } public String getName() { return this.name; } public void setName(final String name) { this.name = name; } public Integer getAge() { return this.age; } protected void setAge(final Integer age) { this.age = age; } }
@ToString
把所有類屬性都編寫到toString方法中方便打印日志,是一件多麼枯燥無味的事情。使用@ToString註解可以自動生成toString方法,默認會包含所有類屬性,使用@ToString.Exclude註解可以排除屬性的生成。
/** * Created by macro on 2020/12/17. */ @ToString public class ToStringExample { @ToString.Exclude private Long id; private String name; private Integer age; public ToStringExample(Long id,String name,Integer age){ this.id =id; this.name = name; this.age = age; } public static void main(String[] args) { ToStringExample example = new ToStringExample(1L,"test",20); //自動實現toString方法,輸出ToStringExample(name=test, age=20) System.out.println(example); } }
編譯後Lombok會生成如下代碼。
public class ToStringExample { private Long id; private String name; private Integer age; public ToStringExample(Long id, String name, Integer age) { this.id = id; this.name = name; this.age = age; } public String toString() { return "ToStringExample(name=" + this.name + ", age=" + this.age + ")"; } }
@EqualsAndHashCode
使用@EqualsAndHashCode註解可以自動生成hashCode和equals方法,默認包含所有類屬性,使用@EqualsAndHashCode.Exclude可以排除屬性的生成。
/** * Created by macro on 2020/12/17. */ @Getter @Setter @EqualsAndHashCode public class EqualsAndHashCodeExample { private Long id; @EqualsAndHashCode.Exclude private String name; @EqualsAndHashCode.Exclude private Integer age; public static void main(String[] args) { EqualsAndHashCodeExample example1 = new EqualsAndHashCodeExample(); example1.setId(1L); example1.setName("test"); example1.setAge(20); EqualsAndHashCodeExample example2 = new EqualsAndHashCodeExample(); example2.setId(1L); //equals方法隻對比id,返回true System.out.println(example1.equals(example2)); } }
編譯後Lombok會生成如下代碼。
public class EqualsAndHashCodeExample { private Long id; private String name; private Integer age; public EqualsAndHashCodeExample() { } public boolean equals(final 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 { 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; } return true; } } } protected boolean canEqual(final Object other) { return other instanceof EqualsAndHashCodeExample; } public int hashCode() { int PRIME = true; int result = 1; Object $id = this.getId(); int result = result * 59 + ($id == null ? 43 : $id.hashCode()); return result; } }
@XxConstructor
使用@XxConstructor註解可以自動生成構造方法,有@NoArgsConstructor、@RequiredArgsConstructor和@AllArgsConstructor三個註解可以使用。
- @NoArgsConstructor:生成無參構造函數。
- @RequiredArgsConstructor:生成包含必須參數的構造函數,使用@NonNull註解的類屬性為必須參數。
- @AllArgsConstructor:生成包含所有參數的構造函數。
/** * Created by macro on 2020/12/17. */ @NoArgsConstructor @RequiredArgsConstructor(staticName = "of") @AllArgsConstructor public class ConstructorExample { @NonNull private Long id; private String name; private Integer age; public static void main(String[] args) { //無參構造器 ConstructorExample example1 = new ConstructorExample(); //全部參數構造器 ConstructorExample example2 = new ConstructorExample(1L,"test",20); //@NonNull註解的必須參數構造器 ConstructorExample example3 = ConstructorExample.of(1L); } }
編譯後Lombok會生成如下代碼。
public class ConstructorExample { @NonNull private Long id; private String name; private Integer age; public ConstructorExample() { } private ConstructorExample(@NonNull final Long id) { if (id == null) { throw new NullPointerException("id is marked non-null but is null"); } else { this.id = id; } } public static ConstructorExample of(@NonNull final Long id) { return new ConstructorExample(id); } public ConstructorExample(@NonNull final Long id, final String name, final Integer age) { if (id == null) { throw new NullPointerException("id is marked non-null but is null"); } else { this.id = id; this.name = name; this.age = age; } } }
@Data
@Data是一個方便使用的組合註解,是@ToString、@EqualsAndHashCode、@Getter、@Setter和@RequiredArgsConstructor的組合體。
/** * Created by macro on 2020/12/17. */ @Data public class DataExample { @NonNull private Long id; @EqualsAndHashCode.Exclude private String name; @EqualsAndHashCode.Exclude private Integer age; public static void main(String[] args) { //@RequiredArgsConstructor已生效 DataExample example1 = new DataExample(1L); //@Getter @Setter已生效 example1.setName("test"); example1.setAge(20); //@ToString已生效 System.out.println(example1); DataExample example2 = new DataExample(1L); //@EqualsAndHashCode已生效 System.out.println(example1.equals(example2)); } }
編譯後Lombok會生成如下代碼。
public class DataExample { @NonNull private Long id; private String name; private Integer age; public DataExample(@NonNull final Long id) { if (id == null) { throw new NullPointerException("id is marked non-null but is null"); } else { this.id = id; } } @NonNull public Long getId() { return this.id; } public String getName() { return this.name; } public Integer getAge() { return this.age; } public void setId(@NonNull final Long id) { if (id == null) { throw new NullPointerException("id is marked non-null but is null"); } else { this.id = id; } } public void setName(final String name) { this.name = name; } public void setAge(final Integer age) { this.age = age; } public boolean equals(final Object o) { if (o == this) { return true; } else if (!(o instanceof DataExample)) { return false; } else { DataExample other = (DataExample)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 false; } } else if (!this$id.equals(other$id)) { return false; } return true; } } } protected boolean canEqual(final Object other) { return other instanceof DataExample; } public int hashCode() { int PRIME = true; int result = 1; Object $id = this.getId(); int result = result * 59 + ($id == null ? 43 : $id.hashCode()); return result; } public String toString() { return "DataExample(id=" + this.getId() + ", name=" + this.getName() + ", age=" + this.getAge() + ")"; } }
@Value
使用@Value註解可以把類聲明為不可變的,聲明後此類相當於final類,無法被繼承,其屬性也會變成final屬性。
/** * Created by macro on 2020/12/17. */ @Value public class ValueExample { private Long id; private String name; private Integer age; public static void main(String[] args) { //隻能使用全參構造器 ValueExample example = new ValueExample(1L,"test",20); // example.setName("andy") //沒有生成setter方法,會報錯 // example.name="andy" //字段被設置為final類型,會報錯 } }
編譯後Lombok會生成如下代碼。
public final class ValueExample { private final Long id; private final String name; private final Integer age; public static void main(String[] args) { new ValueExample(1L, "test", 20); } public ValueExample(final Long id, final String name, final Integer age) { this.id = id; this.name = name; this.age = age; } public Long getId() { return this.id; } public String getName() { return this.name; } public Integer getAge() { return this.age; } }
@Builder
使用@Builder註解可以通過建造者模式來創建對象,建造者模式加鏈式調用,創建對象太方便瞭!
/** * Created by macro on 2020/12/17. */ @Builder @ToString public class BuilderExample { private Long id; private String name; private Integer age; public static void main(String[] args) { BuilderExample example = BuilderExample.builder() .id(1L) .name("test") .age(20) .build(); System.out.println(example); } }
編譯後Lombok會生成如下代碼。
public class BuilderExample { private Long id; private String name; private Integer age; BuilderExample(final Long id, final String name, final Integer age) { this.id = id; this.name = name; this.age = age; } public static BuilderExample.BuilderExampleBuilder builder() { return new BuilderExample.BuilderExampleBuilder(); } public String toString() { return "BuilderExample(id=" + this.id + ", name=" + this.name + ", age=" + this.age + ")"; } public static class BuilderExampleBuilder { private Long id; private String name; private Integer age; BuilderExampleBuilder() { } public BuilderExample.BuilderExampleBuilder id(final Long id) { this.id = id; return this; } public BuilderExample.BuilderExampleBuilder name(final String name) { this.name = name; return this; } public BuilderExample.BuilderExampleBuilder age(final Integer age) { this.age = age; return this; } public BuilderExample build() { return new BuilderExample(this.id, this.name, this.age); } public String toString() { return "BuilderExample.BuilderExampleBuilder(id=" + this.id + ", name=" + this.name + ", age=" + this.age + ")"; } } }
@SneakyThrows
還在手動捕獲並拋出異常?使用@SneakyThrows註解自動實現試試!
/** * Created by macro on 2020/12/17. */ public class SneakyThrowsExample { //自動拋出異常,無需處理 @SneakyThrows(UnsupportedEncodingException.class) public static byte[] str2byte(String str){ return str.getBytes("UTF-8"); } public static void main(String[] args) { String str = "Hello World!"; System.out.println(str2byte(str).length); } }
編譯後Lombok會生成如下代碼。
public class SneakyThrowsExample { public SneakyThrowsExample() { } public static byte[] str2byte(String str) { try { return str.getBytes("UTF-8"); } catch (UnsupportedEncodingException var2) { throw var2; } } }
@Synchronized
當我們在多個線程中訪問同一資源時,往往會出現線程安全問題,以前我們往往使用synchronized關鍵字修飾方法來實現同步訪問。使用@Synchronized註解同樣可以實現同步訪問。
package com.macro.mall.tiny.example; import lombok.*; /** * Created by macro on 2020/12/17. */ @Data public class SynchronizedExample { @NonNull private Integer count; @Synchronized @SneakyThrows public void reduceCount(Integer id) { if (count > 0) { Thread.sleep(500); count--; System.out.println(String.format("thread-%d count:%d", id, count)); } } public static void main(String[] args) { //添加@Synchronized三個線程可以同步調用reduceCount方法 SynchronizedExample example = new SynchronizedExample(20); new ReduceThread(1, example).start(); new ReduceThread(2, example).start(); new ReduceThread(3, example).start(); } @RequiredArgsConstructor static class ReduceThread extends Thread { @NonNull private Integer id; @NonNull private SynchronizedExample example; @Override public void run() { while (example.getCount() > 0) { example.reduceCount(id); } } } }
編譯後Lombok會生成如下代碼。
public class SynchronizedExample { private final Object $lock = new Object[0]; @NonNull private Integer count; public void reduceCount(Integer id) { try { synchronized(this.$lock) { if (this.count > 0) { Thread.sleep(500L); Integer var3 = this.count; Integer var4 = this.count = this.count - 1; System.out.println(String.format("thread-%d count:%d", id, this.count)); } } } catch (Throwable var7) { throw var7; } } }
@With
使用@With註解可以實現對原對象進行克隆,並改變其一個屬性,使用時需要指定全參構造方法。
@With @AllArgsConstructor public class WithExample { private Long id; private String name; private Integer age; public static void main(String[] args) { WithExample example1 = new WithExample(1L, "test", 20); WithExample example2 = example1.withAge(22); //將原對象進行clone並設置age,返回false System.out.println(example1.equals(example2)); } }
編譯後Lombok會生成如下代碼。
public class WithExample { private Long id; private String name; private Integer age; public WithExample withId(final Long id) { return this.id == id ? this : new WithExample(id, this.name, this.age); } public WithExample withName(final String name) { return this.name == name ? this : new WithExample(this.id, name, this.age); } public WithExample withAge(final Integer age) { return this.age == age ? this : new WithExample(this.id, this.name, age); } public WithExample(final Long id, final String name, final Integer age) { this.id = id; this.name = name; this.age = age; } }
@Getter(lazy=true)
當我們獲取某一個屬性比較消耗資源時,可以給@Getter添加 lazy=true
屬性實現懶加載,會生成Double Check Lock 樣板代碼對屬性進行懶加載。
/** * Created by macro on 2020/12/17. */ 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; } public static void main(String[] args) { //使用Double Check Lock 樣板代碼對屬性進行懶加載 GetterLazyExample example = new GetterLazyExample(); System.out.println(example.getCached().length); } }
編譯後Lombok會生成如下代碼。
public class GetterLazyExample { private final AtomicReference<Object> cached = new AtomicReference(); public GetterLazyExample() { } private double[] expensive() { double[] result = new double[1000000]; for(int i = 0; i < result.length; ++i) { result[i] = Math.asin((double)i); } return result; } public double[] getCached() { Object value = this.cached.get(); if (value == null) { synchronized(this.cached) { value = this.cached.get(); if (value == null) { double[] actualValue = this.expensive(); value = actualValue == null ? this.cached : actualValue; this.cached.set(value); } } } return (double[])((double[])(value == this.cached ? null : value)); } }
@Log
使用@Log註解,可以直接生成日志對象log,通過log對象可以直接打印日志。
/** * Created by macro on 2020/12/17. */ @Log public class LogExample { public static void main(String[] args) { log.info("level info"); log.warning("level warning"); log.severe("level severe"); } }
編譯後Lombok會生成如下代碼。
public class LogExample { private static final Logger log = Logger.getLogger(LogExample.class.getName()); public LogExample() { } public static void main(String[] args) { log.info("level info"); log.warning("level warning"); log.severe("level severe"); } }
@Slf4j
使用Lombok生成日志對象時,根據使用日志實現的不同,有多種註解可以使用。比如@Log、@Log4j、@Log4j2、@Slf4j等。
/** * Created by macro on 2020/12/17. */ @Slf4j public class LogSlf4jExample { public static void main(String[] args) { log.info("level:{}","info"); log.warn("level:{}","warn"); log.error("level:{}", "error"); } }
編譯後Lombok會生成如下代碼。
public class LogSlf4jExample { private static final Logger log = LoggerFactory.getLogger(LogSlf4jExample.class); public LogSlf4jExample() { } public static void main(String[] args) { log.info("level:{}", "info"); log.warn("level:{}", "warn"); log.error("level:{}", "error"); } }
Lombok原理
如果IDEA不安裝Lombok插件的話,我們打開使用Lombok的項目是無法通過編譯的。裝瞭以後IDEA才會提示我們Lombok為我們生成的方法和屬性。
使用瞭@Data註解以後,查看類結構可以發現getter、setter、toString等方法。
打開target目錄下的 .class
文件,我們可以看到Lombok為我們生成的代碼,可見Lombok是通過解析註解,然後在編譯時生成代碼來實現Java代碼的功能增強的。
參考資料
官方文檔:https://projectlombok.org/features/all
項目源碼地址
https://github.com/macrozheng/mall-learning/tree/master/mall-tiny-lombok
到此這篇關於Lombok為啥這麼牛逼?SpringBoot和IDEA官方都要支持它的文章就介紹到這瞭,更多相關Lombok SpringBoot和IDEA內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Lombok 安裝和使用小技巧
- Lombok的詳細使用及優缺點總結
- SpringBoot圖文並茂講解Lombok庫的安裝與使用
- 強烈推薦IDEA提高開發效率的必備插件
- Spring Boot整合Lombok的方法詳解