您的位置首页  科技资讯

record是什么意思(new record是什么意思)

记录类型在 Java 14 中以预览功能的形式引入,在 Java 16 中成为正式功能。Java 17 作为 Java 目前的长期支持版本,在很

record是什么意思(new record是什么意思)

 

记录类型(Record)在 Java 14 中以预览功能的形式引入,在 Java 16 中成为正式功能Java 17 作为 Java 目前的长期支持(LTS)版本,在很长的一段时间内,会是 Java 开发时的首选 Java 版本。

了解 Java 17 中可以使用的新特性,对于提升开发效率是很有必要的本文介绍的记录类型就是一个非常重要的新特性为什么要有记录类型?提到记录类型,首先就要介绍值对象(Value Object)值对象与实体(Entity)相对应。

两者的区别在于:每个实体对象都有其标识符(ID),可以是业务相关的,也可以是系统生成的无意义的标识符比如,自然人这一类实体对象的标识符可以是身份证号码,而订单这一类实体的标识符则通常是随机生成的 UUID。

值对象没有标识符,通常只是作为数据的容器,由多个字段组成值对象一般是不可变的,其相等性由所包含的字段来确定当且仅当所包含的字段相等时,两个值对象才会被认为是相等的实体的相等性则由其标识符来确定值对象在开发中经常会遇到。

我们通常会用到的数据传输对象(Data Transfer Object,DTO),也是值对象的一种在记录类型出现之前,编写值对象的 Java 类是一件比较繁琐的事情由于值对象的这些特征,值对象的 Java 类都有明显的相似性:。

类中的字段都声明为 private final,全部字段的值都在构造器中设置提供了访问字段值的方法覆写了 equals 方法来提供基于所包含的字段的相等性的比较方式,同时也必须覆写 hashCode 方法。

覆写了 toString 方法来生成有意义的描述信息,方便调试和排查问题以表示地理位置的 GeoLocation 为例来说明GeoLocation 中有两个字段,分别是经度(lng)和维度(lat)从中可以看到,即便是两个字段的值对象,对应的 Java 类也是很冗长的,包含了很多 boilerplate 的代码。

import java.util.Objects; publicclassGeoLocation{ privatefinal double lng; privatefinal double lat;

public GeoLocation(double lng, double lat) { this.lat = lat; this.lng = lng; } public double getLng() {

returnthis.lng; } public double getLat() { returnthis.lat; } @Overridepublic boolean equals(Object o) {

if (this == o) { returntrue; } if (o == null || this.getClass() != o.getClass()) {

returnfalse; } GeoLocation that = (GeoLocation) o; returnDouble.compare(that.lng, this.lng) ==

0 && Double.compare(that.lat, this.lat) == 0; } @Overridepublic int hashCode() { return

Objects.hash(this.lng, this.lat); } @Overridepublic String toString() { return"GeoLocation{"

+ "lng=" + this.lng + ", lat=" + this.lat + }; } }虽然 IDE 已经可以提供帮助来自动生成这些代码,它们出现在源代码中仍然显得很冗余。

为了简化使用,很多 Java 项目都选择使用第三方库来解决,最常用的库是 Lombok下面的代码是使用 Lombok 的 GeoLocation 类,使用了 Lombok 提供的 @Value 注解与上面的版本相比,Lombok 的版本可以少写很多代码。

值对象所需要的方法,由 Lombok 自动生成import lombok.Value; @ValuepublicclassGeoLocation{ double lng; double lat; }

记录类型的出现,使得 Java 有了原生的表示值对象的方式,而不再依赖第三方库记录类型怎么描述值对象?对于同样的 GeoLocation 值对象,使用记录类型的描述方式如下所示这里使用了新的关键词 record

public record GeoLocation(double lng, double lat){ }记录类型是一种受限的 Java 类受限类这个名词可能有点陌生但是提到 Java 中的另外一种受限类型,枚举类,你应该就明白受限类的含义了。

受限类在使用时有一些限制,只能支持特定的使用场景记录类型使用关键词 record 来描述,类似于枚举类型的 enum记录类型是值的聚合记录类型中的值称为记录的组件在 GeoLocation 记录类型中,lng 和 lat 都是记录组件。

每个记录组件都有名称和类型对于每个记录组件,在生成的 Java 类中,都有一个 private、final 和 非 static 的字段与之对应需要注意的是,记录类型的访问字段的方法名称,并不是 Java Bean 的 getXXX 或 isXXX 的格式,而是直接使用的记录组件的名称。

比如,GeoLocation 中的对应方法名称是 lng() 和 lat()记录类型是不可变的所有记录组件都在构造器中初始化如果记录类没有显式地声明一个构造器,编译器会自动生成一个自动生成的构造器的形式参数列表与记录类型的组件声明是相同的,具体的实现也很简单,就是把形式参数的值赋值给对应的字段。

这一点与手写的 Java 类是相同的有些记录类型需要对组件的值进行校验比如,GeoLocation 中的经度的范围是 -180 到 180,纬度的范围是 -90 到 90这个时候可以添加一个自定义的构造器。

下面的代码给出了 GeoLocation 的自定义构造器的示例public record GeoLocation(double lng, double lat){ publicGeoLocation

(double lng, double lat){ if (lng = -180) { this.lng = lng; } else {

thrownew IllegalArgumentException("经度值无效"); } if (lat = -90) { this.lat = lat; }

else { thrownew IllegalArgumentException("纬度值无效"); } } }需要注意的是,这种形式的构造器必须对所有的记录组件都初始化即便是不需要校验的组件,也需要添加类似 this.xyz = xyz; 这样的代码来进行初始化,否则会出现编译错误。

如果记录类型的组件很多,需要额外进行验证的字段又很少,使用这种方式的构造器就比较繁琐了这个时候可以使用紧凑形式的构造器下面代码中的记录类型 Book,只有组件 isbn 需要验证,其他的组件都会由编译器自动添加赋值操作。

public record Book(String isbn, String title, String description, BigDecimal price) {

public Book { if (isbn == null) { thrownew IllegalArgumentException("ISBN 无效"); } } }

最后使用 javap 命令查看一下记录类型生成的字节代码下面是 GeoLocation 对应的字节代码,可以看到由编译器生成的各种方法Compiled from "GeoLocation.java"public

finalclassio.vividcode.java11to17.record.record.GeoLocationextendsjava.lang.Record{ public io.vividcode.java11to17.record.record.GeoLocation(

double, double); publicfinal java.lang.String toString(); publicfinalinthashCode(); publicfinal

booleanequals(java.lang.Object); publicdoublelng(); publicdoublelat(); }记录类型可以用在什么地方?记录类型在很多场合都有其应用。

记录类型最重要的作用是描述值对象记录类型的语法简洁,不需要第三方库的支持记录类型也支持嵌套,对于一个复杂的对象结构,可以很容易就创建与之对应的记录类型结构下面代码中的记录类型 Order 及其嵌套的记录类型 LineItem 和 Address,可以描述订单相关的对象结构。

public record Order(String orderId, String userId, LocalDateTime createdAt, List lineItems, Address deliveryAddress) {

public record LineItem(String productId, int quantity, BigDecimal price) { } public record Address(

String addressLine, String cityId, String provinceId, String zipCode) { } }

不过与 Lombok 相比,记录类型还是缺少了一些功能比如,没有内置提供对 builder 模式的支持,创建对象必须使用构造方法对于 builder 模式的问题,可以使用第三方库解决,如 GitHub 上的 record-builder (Randgalt/record-builder)。

记录类型的第二个用法是作为方法的返回值方法只能有一个返回值当需要返回多个值时,需要把这些值封装起来一般的做法是创建新的 Java 类有些人会使用通用的类,比如封装两个值的 Pair,封装三个值的 Triple,封装更多值的 Tuple 等。

有些人还会使用通用的数据结构,如 List 或 Map 等这些做法都不够直观,影响代码的可读性有了记录类型之后,可以简洁地用记录类型来封装多个返回值记录类型的第三个用法是表示实际的值这些指的是领域模型中的值对象。

比如表示地理位置坐标的 GeoLocation,表示二维坐标的 Position 等使用记录类型更贴合这些对象原本的语义记录类型可以声明在方法实现中,称为本地记录类型当需要在一个方法中进行复杂的计算时,可以使用本地记录类型来表示中间的计算结果。

这样写出来的代码更容易阅读和理解这一点在使用 Java Stream API 的时候尤为明显流处理的中间结果可以用记录类型来表示,从而把一个很长的处理流切分成较小的流,代码可读性更好下面的代码展示了本地记录类型的用法。

OrderTotal 是一个本地记录类型,表示单个订单的总金额在使用 Java 流进行计算时,首先把输入流转换成 OrderTotal 表示的中间结果,再进行下一步的计算publicclassOrderCalculator

{ public Map calculate(List orders) { record OrderTotal(String orderId, BigDecimal total) { } Map

List> orderTotal = orders.stream() .collect( Collectors.groupingBy(Order::userId, Collectors.mapping(order -> { BigDecimal total = order.lineItems().stream() .map(item -> item.price() .multiply(BigDecimal.valueOf(item.quantity()))) .reduce(BigDecimal.ZERO, BigDecimal::add);

returnnew OrderTotal(order.orderId(), total); }, Collectors.toList()))); return orderTotal.entrySet().stream().map(entry ->

new OrderSummary(entry.getKey(), entry.getValue().stream() .max(Comparator.comparing(OrderTotal::total)) .map(OrderTotal::total).orElse(BigDecimal.ZERO))) .collect(Collectors.toMap(OrderSummary::userId,

Function.identity())); } }记录类型可以封装方法的多个参数如果一个方法的参数多于4个,使用起来就会很麻烦,尤其是当这些参数的类型一样时使用记录类型封装参数可以使得代码更简洁当参数发生变化时,并不需要修改调用者的代码。

不过这种方式的使用也存在一些争议Java 中并没有 Kotlin 的对象解构语法,在方法中使用这些参数还需要调用额外的访问方法另外准备这些参数对象的时候,还是一样需要使用记录类型的构造方法,会同样遇到参数过多的问题。

所以,记录类型的这种用法可以酌情使用

免责声明:本站所有信息均搜集自互联网,并不代表本站观点,本站不对其真实合法性负责。如有信息侵犯了您的权益,请告知,本站将立刻处理。联系QQ:1640731186