譯者 | 胥磊
審校 | 梁策 孫淑娟
很長(zhǎng)時(shí)間以來(lái),Java 都因其冗長(zhǎng)而受到一些開(kāi)發(fā)者的詬病。哪怕是最熱衷 Java 的開(kāi)發(fā)者或許也不得不承認(rèn),聲明一個(gè)只有兩個(gè)屬性的 bean 類Java讓人覺(jué)得有點(diǎn)可笑。因?yàn)槿绻裱扑]規(guī)范,最終不僅添加了 getter 和 setter方法,還要添加toString, hashcode 和 equals 方法的重寫,最終大塊的樣板文件式的代碼逼得開(kāi)發(fā)者想放棄Java語(yǔ)言。
Java
import java.util.Objects;
public class Car {
private String brand;
private String model;
private int year;
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
@Override
public String toString() {
return "Car{" +
"brand='" + brand + '\'' +
", model='" + model + '\'' +
", year=" + year +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Car car = (Car) o;
return year == car.year && Objects.equals(brand, car.brand) && Objects.equals(model, car.model);
}
@Override
public int hashCode() {
return Objects.hash(brand, model, year);
}
}
幸運(yùn)的是,Lombok的橫空出世大大減輕了Java開(kāi)發(fā)者的痛苦。但自從有了與其作用相似的Java Record類型,有人可能會(huì)問(wèn):Record是否可以全面取代Lombok呢?
1.Lombok是什么?
Lombok是一個(gè)與開(kāi)發(fā)環(huán)境高度集成的 Java 類庫(kù)(當(dāng)然也可以看成一種語(yǔ)法糖),通過(guò)注解改進(jìn)(spice)代碼,它在 Java 社區(qū)中被廣泛接受和使用。
使用了Lombok后,我們新建一個(gè)名為Car的類是這樣的:
Java
import lombok.Data;
@Data
public class Car {
private String brand;
private String model;
private int year;
}
代碼更加簡(jiǎn)潔,同時(shí)也不會(huì)影響之前版本的任何功能。
2.Java Record是什么?
定義的每一個(gè)Java Record類型可以簡(jiǎn)單地看做是值對(duì)象(Value Object)模式的實(shí)現(xiàn)。它本質(zhì)還是一個(gè) Java 類,其中所有的屬性都是final的。所以在創(chuàng)建對(duì)象時(shí)所有類屬性都需要傳遞。Java Record是在 Java 14中引入的,它將持續(xù)改進(jìn),提升類設(shè)計(jì)。
通過(guò)Record新建Car類是這樣的:
Java
public record Car(String brand, String model, int year)
{
與前一個(gè)版本對(duì)比,改進(jìn)非常明顯。
下文將分析Lombok的一些特性,并通過(guò)和Record進(jìn)行比較來(lái)評(píng)估是否可以永久讓Lombok退出歷史舞臺(tái)。
3.不可變性
Record默認(rèn)情況下是不可變的,這意味著所有的類屬性都被隱式的聲明為 final。我們通常認(rèn)為Record和值對(duì)象(Value Objects)很相似,但是它們沒(méi)有 setter 方法,所有的值都需要在構(gòu)造函數(shù)中傳遞。Lombok可以使用@Value 注解達(dá)成同樣的效果,但也可以使用@Data注解來(lái)保持可變性。
Java
import lombok.Value;
@Value
public class Car {
private String brand;
private String model;
private int year;
}
4.Bean公約
Record并不打算遵循 bean的公約,獲取對(duì)象的方法不使用 getX 的方式命名,同時(shí)也不再提供 setter 方法和無(wú)參的構(gòu)造函數(shù)。另一方面,Lombok只需使用@Data注解就可以將一個(gè)類輕松轉(zhuǎn)換為 JavaBean。
5.Builder
Builder構(gòu)建器模式是改善對(duì)象、創(chuàng)建語(yǔ)法很棒的一種設(shè)計(jì)模式。Lombok為我們提供了@Builder這個(gè)很實(shí)用的注解,它幫我們實(shí)現(xiàn)了所有樣板代碼。到目前為止,Java Record 并不打算提供此類實(shí)現(xiàn)。
Java
import lombok.Builder;
@Builder
public class Car {
private String brand;
private String model;
private int year;
public static void main(String[] args) {
Car myCamaro = Car.builder()
.brand("Chevrolet")
.model("Camaro")
.year(2022)
.build();
}
}
6.多fields類
Record只對(duì)少量fields的類是友好的。但是,如果再向其中添加10個(gè)fields,那么得到的會(huì)是一個(gè)龐大的構(gòu)造函數(shù)(繁多的入?yún)?,隨之而來(lái)的還有多參構(gòu)造函數(shù)所帶來(lái)的固有的問(wèn)題(傳參易錯(cuò)位,方法重載難判斷等)。
Java
public record DetailedCar(
String brand, String model, int year,
String engineCode, String engineType, String requiredFuel,
String fuelSystem, String maxHorsePower, String maxTorque,
float fuelCapacity) {
}
Java
DetailedCar camaroDetailed = new DetailedCar(
"Chevrolet", "Camaro", 2022, "LTG", "Turbocharged",
"Gas I4", "Direct Injection", "275 @ 560", "295 @ 3000-4500",
19.0f);
使用了Lombok,我們就可以決定創(chuàng)建bean類是選擇使用 setter來(lái)設(shè)置對(duì)象的狀態(tài),還是使用builder這種更簡(jiǎn)潔的方式來(lái)構(gòu)造實(shí)例。唯一需要注意的是,因?yàn)槠淠J(rèn)不會(huì)強(qiáng)制設(shè)置所有屬性,所以可能使實(shí)例處于屬性不完整狀態(tài)。當(dāng)然@Builder 注解支持我們將類中所有屬性標(biāo)記為@nonNull,這樣在構(gòu)建時(shí)屬性就是必需的。如果必需屬性缺失設(shè)置則會(huì)在運(yùn)行時(shí)拋出一個(gè)異常,而不是編譯時(shí)強(qiáng)制拋出異常。
Java
import lombok.Builder;
import lombok.NonNull;
@Builder
public class DetailedCar {
@NonNull
private String brand;
@NonNull
private String model;
@NonNull
private int year;
@NonNull
private String engineCode;
@NonNull
private String engineType;
@NonNull
private String requiredFuel;
@NonNull
private String fuelSystem;
@NonNull
private String maxHorsePower;
@NonNull
private String maxTorque;
@NonNull
private float fuelCapacity;
public static void main(String[] args) {
DetailedCar camaroIncomplete = DetailedCar.builder()
.brand("Chevrolet")
.model("Camaro")
.year(2022)
.build();
}
}
輸出:
Exception in thread "main" java.lang.NullPointerException: engineCode is marked non-null but is null
7.繼承
到目前為止,Java Record類是不支持繼承的,所以不能通過(guò)擴(kuò)展其他Record類來(lái)創(chuàng)建一個(gè)新的Record類,這可能是模型設(shè)計(jì)的一個(gè)限制。盡管如此,我們也要認(rèn)識(shí)到組合優(yōu)于繼承(面向?qū)ο笤O(shè)計(jì)原則之七)。
Java
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class Car extends MotorVehicle {
private String brand;
private String model;
private int year;
}
8.結(jié)論
Record是Java的一個(gè)極佳的新特性,它正推動(dòng)代碼向更簡(jiǎn)潔的方向發(fā)展,因此應(yīng)該多多使用。對(duì)于提供了眾多功能的Lombok,考慮到Java變更的緩慢速度,要在項(xiàng)目中將其徹底取代似乎還為時(shí)尚早。
原文鏈接:https://dzone.com/articles/records-vs-lombok
譯者介紹
胥磊,51CTO社區(qū)編輯,某頭部電商技術(shù)副總監(jiān),關(guān)注Java后端開(kāi)發(fā),技術(shù)管理,架構(gòu)優(yōu)化,分布式開(kāi)發(fā)等領(lǐng)域。