lombok

Posted by Adam on August 24, 2022
### [lombok](https://kucw.github.io/blog/2020/3/java-lombok/) pom.xml ```xml <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.26</version> <scope>provided</scope> </dependency> ``` build.gradle ```groovy repositories { mavenCentral() } dependencies { compileOnly 'org.projectlombok:lombok:1.18.26' annotationProcessor 'org.projectlombok:lombok:1.18.26' testCompileOnly 'org.projectlombok:lombok:1.18.26' testAnnotationProcessor 'org.projectlombok:lombok:1.18.26' } ``` ## @Data ```java @Data @NoArgsConstructor @AllArgsConstructor public class User { private Integer id; private String name; } ``` same ```java @Getter @Setter @ToString @EqualsAndHashCode @RequiredArgsConstructor @NoArgsConstructor @AllArgsConstructor public class User { private Integer id; private String name; } ``` --- ## @Builder 下面是一個使用lombok的@Builder註釋的範例: ```java import lombok.Builder; import lombok.Getter; @Getter @Builder public class User { private String name; private int age; private String email; } public class Main { public static void main(String[] args) { User user = User.builder() .name("Alice") .age(25) .email("alice@example.com") .build(); System.out.println("Name: " + user.getName()); System.out.println("Age: " + user.getAge()); System.out.println("Email: " + user.getEmail()); } } ``` 在這個範例中,我們使用@Builder註釋來自動生成一個builder模式的建造者類,讓我們可以方便地創建對象。在Main類中,我們使用User.builder()方法來創建User對象,然後使用builder()方法設置屬性的值,最後使用build()方法來建構User對象。 --- ## @SneakyThrows ### 💻 實戰演練:怎麼用?前後對比給你看 我們以最常拋出 `UnsupportedEncodingException`(受檢異常)的 URL 編碼為例: #### ❌ 傳統寫法(被 try-catch 搞得支離破碎) ```java // 只是想做個 URL 編碼,程式碼就被迫長出厚厚的腫瘤 public String encodeUrlTraditional(String text) { try { return URLEncoder.encode(text, "UTF-8"); // 👈 這行會拋出受檢異常 } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); // 逼得你必須手動轉成 RuntimeException } } ``` #### ⭕ 現代優雅寫法:使用 `@SneakyThrows` ```java import lombok.SneakyThrows; // 😎 乾淨、清爽、直接一行流 @SneakyThrows public String encodeUrlModern(String text) { return URLEncoder.encode(text, "UTF-8"); // 👈 編譯器直接放行,再也不會嗶嗶叫! } ``` #### 🌟 進階玩法:精準防禦(推薦寫法) 如果你不希望所有的異常都被矇混過去,只希望針對「特定的受檢異常」放行,你可以把異常的 Class 當作參數傳給它: ```java @SneakyThrows(UnsupportedEncodingException.class) public String encodeUrlStrict(String text) { return URLEncoder.encode(text, "UTF-8"); } ``` ```java // 🌟 完美點名:同時欺騙編譯器放行「編碼異常」與「資料庫異常」 @SneakyThrows({UnsupportedEncodingException.class, SQLException.class}) public String processLegacyData(String rawData) { // 1. 可能拋出 UnsupportedEncodingException (受檢) String decoded = java.net.URLDecoder.decode(rawData, "UTF-8"); // 2. 可能拋出 SQLException (受檢) String dbResult = jdbcTool.queryData(decoded); return dbResult; } ``` --- ## @Accessors ### 🎯 直擊核心 它是 Lombok 家族中的「美學大師」,專門用來顛覆 Java 傳統的 `set / get` 語法,幫你開啟「流式鏈式呼叫(Fluent / Chain)」的語法糖! 在標準的 Java Bean 規範中,當你對一個物件賦值時,語法通常非常呆板、枯燥: ```java User user = new User(); user.setName("Gemini"); user.setAge(18); user.setAddress("Taipei"); ``` 這種一行一個 `set` 的寫法,會強迫程式碼像階梯一樣往垂直方向瘋狂延伸。而 `@Accessors` 的唯一作用,就是**重新定義 Getter 與 Setter 的生成骨架**,讓你能寫出像 JavaScript、流式 API(Stream)那樣極致流暢的一行流代碼! --- ### 🔍 深度拆解:它最經典的「三大核心參數」 `@Accessors` 通常會跟 `@Data` 或 `@Getter / @Setter` 搭配使用。它有三個非常強大的屬性設定,能徹底改變你實體物件(Entity/DTO)的長相: #### 1. `chain = true`(商用高頻最愛 🌟) * **技術原理**:傳統的 `set` 方法回傳型態是 `void`(丟進去就沒了)。當你開啟 `chain = true`,Lombok 會在編譯期把所有 `set` 方法改造成「回傳 `this`(物件本身)」! * **語法神效**: ```java import lombok.Data; import lombok.experimental.Accessors; @Data @Accessors(chain = true) // 🌟 開啟鏈式流 public class User { private String name; private int age; } // === 呼叫端直接高潮,一行收工 === User user = new User().setName("Gemini").setAge(18); ``` #### 2. `fluent = true`(極簡主義者的最愛 🚀) * **技術原理**:它嫌 `get` 和 `set` 這五個英文字母太累贅。開啟 `fluent = true` 後,它生成的 Getter/Setter 會**徹底消滅 `get` 與 `set` 前綴**,直接用「欄位名稱」當作方法名。 * **注意**:它天生就自帶 `chain = true` 的效果。 * **語法神效**: ```java @Data @Accessors(fluent = true) // 🚀 開啟極簡流 public class User { private String name; private int age; } // === 賦值與取值,徹底告別 get/set 前綴 === User user = new User().name("Gemini").age(18); // 這是在 set String name = user.name(); // 這是在 get! ``` #### 3. `prefix = "..."`(老舊資料庫欄位的解毒劑 🛠️) * **技術原理**:有時候公司的舊資料庫非常囉唆,欄位天生自帶前綴(例如 `p_name`、`p_age`,代表 private)。但你希望在 Java 程式碼裡面呼叫時是乾淨的 `getName()`、`getAge()`。 * **語法神效**: ```java @Data @Accessors(prefix = "p") // 🛠️ 自動過濾 "p" 前綴 public class Product { private Long pId; private String pName; } // === 呼叫端完全感受不到噁心的資料庫前綴 === Product product = new Product(); product.setId(1001L); // 自動對應到 pId product.setName("Apple"); // 自動對應到 pName ``` --- ### ⚠️ 誠實警告 爽用美學時,必須防禦的三大「商用巨坑」 雖然 `@Accessors(chain = true)` 寫起來爽度破表,但在大型專案(尤其是涉及大量框架反射)中,它埋下了三個可能讓你加班到深夜的致命隱憂: 1. **傳統 Bean 複製工具直接變「瞎子」(如 BeanUtils)**: 像是 Apache 的 `BeanUtils.copyProperties()` 或一些老舊的 DTO 轉換工具,它們的底層邏輯非常死板:**「認定 Setter 方法的回傳值必須是 `void`」**。一旦你加了 `chain = true` 讓 Setter 回傳了 `this`,這些工具在反射時會直接判定這個物件「沒有標準的 Setter」,導致**欄位全部複製失敗,通通變成 null**!(商用環境建議改用現代的 MapStruct,它完美支援鏈式物件)。 2. **與 JSON 序列化框架的相容性考驗**: 當你開啟 `fluent = true`,Getter 變成了 `user.name()`。某些極度老舊的 JSON 序列化套件(如十幾年前的 Jackson 舊版本或某些奇葩的 XML 解析器),在把物件轉成 JSON 字串時,會死板地去掃描 `getXXX` 方法。因為你把 `get` 刪光了,它可能會判定這個物件沒有任何欄位,最後**轉出一個空 JSON `{}**`。 3. **可讀性混淆(特別是 `fluent = true`)**: 在 Code Review 時,看到 `user.age()`,新來的同事可能會一頭霧水:「這行到底是在取值,還是在設定值?」必須看它有沒有帶參數才能判斷,這在大型團隊中會增加一絲絲的溝通成本。 ---