# 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();
```