# Java Lombok 踩坑紀錄 Lombok 一直是我在 Java 開發時拿來幫 POJO 減少一堆煩人的 getter, setter 時非常好用的一個 library,但有時候在設定上還是會覺得很煩,今天記錄一下又遇到了哪些設定上的問題。 前面會先提一下目前使用到的一些 feature,以及他們彼此作用後會產生的一些問題。 ## Immutable classes - Using @Value ### without lombok ```java public class User{ private final long id; private final String name; public User(long id, String name){ this.id = id; this.name = name; } public long getId(){ return id; } public String getName(){ return name; } } ``` ### with lombok ```java @Value public class User{ long id; String name; } ``` > @Value is the immutable variant of @Data; all fields are made private and final by default, and setters are not generated. 直接幫你把 field 設定為 `private final` 並且把 default constructor 也建置出來。 ## builder API - @Builder 有時候類別的屬性非常多,我們也不是每次都需要設定,只想跟使用的情境建構出想要的物件。 ### without lombok ```java public class User{ private final long id; private final String name; private final int age; private final String habit; private final int height; private final int weight; public User(long id, String name, int age, String habit, int height, int weight){ this.id = id; this.name = name; this.age = age; this.habit = habit; this.height = height; this.weight = weight; } // getter 族繁不及備載 } ``` ### with lombok ```java @Value @Builder public class User{ long id; String name; int age; String habit; int height; int weight; } ``` 而 builder API 在這種情況下使用起來特別靈活 All argements constructor ```java final User onlyIdAndNameUser = new User(1, "timm", 0, null, 0, 0, 0); ``` builder API ```java final User onlyIdAndNameUser = User.builder().id(1).name("timm").build(); ``` ## 開始踩坑了 目標是有個 UserAdditionalInfo 需要繼承 User,所以開始一樣的套路加上 lombok annotation。 ```java @Value @Builder public class User{ long id; String name; int age; String habit; int height; int weight; } @Value @Builder public class UserAdditionalInfo extends User{ String skin; } ``` `UserAdditionalInfo` 出現 compile error (**There is no default constructor available in 'xxx.xxx.User'**) 一樣使用 lombok 幫 User 建立一個 default constructor ```java @Value @Builder @NoArgsConstructor public class User{ long id; String name; int age; String habit; int height; int weight; } @Value @Builder public class UserAdditionalInfo extends User{ String skin; } ``` 恩~ compile error 從 `UserAdditionalInfo` 消失,換到了 User 顯示 (**Variable 'id' might not have been initialized , Variable 'name' might not have been initialized...**) 疑,為什麼沒有給定初始值呢?不是加了 **@NoArgsConstructor**,回過頭再看一次文件 >@NoArgsConstructor will generate a constructor with no parameters. If this is not possible (because of final fields), a compiler error will result instead, unless @NoArgsConstructor(force = true) is used, then all final fields are initialized with 0 / false / null. 看來是因為我們前面設定的 @Value 將 User Class 變成了 immutable class,所以這些 field 在這個情況下就沒有被給定初始值了,那解決方法上面有說了,加上 **force = true**。 ```java @Value @Builder @NoArgsConstructor(force = true) public class User{ long id; String name; int age; String habit; int height; int weight; } @Value @Builder public class UserAdditionalInfo extends User{ String skin; } ``` 還是錯!compile error 換成了 (**Lombok @Builder needs a proper constructor for this class**),這邊需要再加上 **@AllArgsConstructor** 給 User。 ```java @Value @Builder @NoArgsConstructor(force = true) @AllArgsConstructor public class User{ long id; String name; int age; String habit; int height; int weight; } @Value @Builder public class UserAdditionalInfo extends User{ String skin; } ``` 到目前為止,compile error 都已經處理完畢,想說可以接著開始使用 `UserAdditionalInfo` builder API 了,殊不知...。 ## builders with fields from superclasses ```java final User onlyIdAndNameUser = UserAdditionalInfo.builder()....; // 誒,為什麼沒有 id, name, age 這些 User fields 可以設定??? ``` workaround ```java @Value public class UserAdditionalInfo extends User{ String skin; @Builder public UserAdditionalInfo(long id, String name, int age, String habit, int height, int weight, String skin){ super(long id, String name, int age, String habit, int height, int weight); this.skin = skin; } } ``` 你在跟我開玩笑吧!就是不想要寫一堆這種建構子才用了 lombok 的啊! 有沒有其他的方式可以讓 builder 出現 super class 的 field? ## @SuperBuilder > @SuperBuilder was introduced as experimental feature in lombok v1.18.2. > > The @SuperBuilder annotation produces complex builder APIs for your classes. In contrast to @Builder, @SuperBuilder also works with fields from superclasses. However, it only works for types. Most importantly, it requires that all superclasses also have the @SuperBuilder annotation. 要改還要 User + UserAdditionalInfo 兩個一起改 @SuperBuilder 才有用 ```java @Value @SuperBuilder @NoArgsConstructor(force = true) @AllArgsConstructor public class User{ long id; String name; int age; String habit; int height; int weight; } @Value @SuperBuilder public class UserAdditionalInfo extends User{ String skin; } ``` 經過設定後,`UserAdditionalInfo` 也可以設定 `User` (super class) 的 field 了。 ```java! final User onlyIdAndNameSkinUser = UserAdditionalInfo.builder().id(1).name("timm").skin("yellow").build(); ```