本文将探讨下 JDK14 后新增的 Record 类型,作为数据载体的这一新类型是否可以完全地取代 Lombok。

译者 | 胥磊

审校 | 梁策 孙淑娟

很长时间以来,Java 都因其冗长而受到一些开发者的诟病。哪怕是最热衷 Java 的开发者或许也不得不承认,声明一个只有两个属性的 bean 类Java让人觉得有点可笑。因为如果遵循推荐规范,最终不仅添加了 getter 和 setter方法,还要添加toString, hashcode 和 equals 方法的重写,最终大块的样板文件式的代码逼得开发者想放弃Java语言。

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);}}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.

幸运的是,Lombok的横空出世大大减轻了Java开发者的痛苦。但自从有了与其作用相似的Java Record类型,有人可能会问:Record是否可以全面取代Lombok呢?

1.Lombok是什么?

Lombok是一个与开发环境高度集成的 Java 类库(当然也可以看成一种语法糖),通过注解改进(spice)代码,它在 Java 社区中被广泛接受和使用。

使用了Lombok后,我们新建一个名为Car的类是这样的:

Java

import lombok.Data;@Data
public class Car {private String brand;private String model;private int year;}1.2.3.4.5.6.7.

代码更加简洁,同时也不会影响之前版本的任何功能。

2.Java Record是什么?

定义的每一个Java Record类型可以简单地看做是值对象(Value Object)模式的实现。它本质还是一个 Java 类,其中所有的属性都是final的。所以在创建对象时所有类属性都需要传递。Java Record是在 Java 14中引入的,它将持续改进,提升类设计。

通过Record新建Car类是这样的:

Java

public record Car(String brand, String model, int year) {1.2.

与前一个版本对比,改进非常明显。

下文将分析Lombok的一些特性,并通过和Record进行比较来评估是否可以永久让Lombok退出历史舞台。

3.不可变性

Record默认情况下是不可变的,这意味着所有的类属性都被隐式的声明为 final。我们通常认为Record和值对象(Value Objects)很相似,但是它们没有 setter 方法,所有的值都需要在构造函数中传递。Lombok可以使用@Value 注解达成同样的效果,但也可以使用@Data注解来保持可变性。

Java

import lombok.Value;@Value
public class Car {private String brand;private String model;private int year;}1.2.3.4.5.6.7.

4.Bean公约

Record并不打算遵循 bean的公约,获取对象的方法不使用 getX 的方式命名,同时也不再提供 setter 方法和无参的构造函数。另一方面,Lombok只需使用@Data注解就可以将一个类轻松转换为 JavaBean。

5.Builder

Builder构建器模式是改善对象、创建语法很棒的一种设计模式。Lombok为我们提供了@Builder这个很实用的注解,它帮我们实现了所有样板代码。到目前为止,Java Record 并不打算提供此类实现。

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();}}1.2.3.4.5.6.7.8.9.10.11.12.13.14.

6.多fields类

Record只对少量fields的类是友好的。但是,如果再向其中添加10个fields,那么得到的会是一个庞大的构造函数(繁多的入参),随之而来的还有多参构造函数所带来的固有的问题(传参易错位,方法重载难判断等)。

Java

public record DetailedCar(String brand, String model, int year,String engineCode, String engineType, String requiredFuel,String fuelSystem, String maxHorsePower, String maxTorque,float fuelCapacity) {}1.2.3.4.5.6.

Java

DetailedCar camaroDetailed = new DetailedCar("Chevrolet", "Camaro", 2022, "LTG", "Turbocharged","Gas I4", "Direct Injection", "275 @ 560", "295 @ 3000-4500",19.0f);1.2.3.4.

使用了Lombok,我们就可以决定创建bean类是选择使用 setter来设置对象的状态,还是使用builder这种更简洁的方式来构造实例。唯一需要注意的是,因为其默认不会强制设置所有属性,所以可能使实例处于属性不完整状态。当然@Builder 注解支持我们将类中所有属性标记为@nonNull,这样在构建时属性就是必需的。如果必需属性缺失设置则会在运行时抛出一个异常,而不是编译时强制抛出异常。

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();}}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.

输出:

Exception in thread "main" java.lang.NullPointerException: engineCode is marked non-null but is null1.

7.继承

到目前为止,Java Record类是不支持继承的,所以不能通过扩展其他Record类来创建一个新的Record类,这可能是模型设计的一个限制。尽管如此,我们也要认识到组合优于继承(面向对象设计原则之七)。

Java

@Data
@ToString(callSuper = true)@EqualsAndHashCode(callSuper = true)public class Car extends MotorVehicle {private String brand;private String model;private int year;}1.2.3.4.5.6.7.8.

8.结论

Record是Java的一个极佳的新特性,它正推动代码向更简洁的方向发展,因此应该多多使用。对于提供了众多功能的Lombok,考虑到Java变更的缓慢速度,要在项目中将其彻底取代似乎还为时尚早。

原文链接:https://dzone.com/articles/records-vs-lombok

译者介绍

胥磊,51CTO社区编辑,某头部电商技术副总监,关注Java后端开发,技术管理,架构优化,分布式开发等领域。