编程知识 cdmana.com

Understanding Java record types

Mention the record type , Let's first introduce the value object . Those familiar with domain driven design should have heard of the concepts of entity and value object .

The value object

Each entity has a unique identifier , It can be business ID Or assigned by the application ID. The equality of entities is entirely determined by identifiers .

A value object has no identifier , Usually used as a container for data . The equality of value objects is determined by the equality of the properties they contain .

Value objects have many application scenarios :

  • Describe the actual values in the business , For example, for name value pairs  Pair, Representing two-dimensional coordinates  Point.

  • Returns multiple values from a method . Multiple values are organized in a value object to return .

  • Parameters of the organization method . If the method has more than one parameter , You can organize these parameters into a value object , It can simplify the use of the method , It is also conducive to code refactoring .

Value objects are usually immutable . After creation, the value of the attribute will not be modified .

Use Java Represents a value object

The traditional method of representing value objects is to use ordinary Java class , The following code is the value object  GeoLocation  class .

import java.util.Objects;

public class GeoLocation {

private final double lng;
private final double lat;

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

public double getLng() {
return lng;
}

public double getLat() {
return lat;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
GeoLocation that = (GeoLocation) o;
return Double.compare(that.lng, lng) == 0
&& Double.compare(that.lat, lat) == 0;
}

@Override
public int hashCode() {
return Objects.hash(lng, lat);
}

@Override
public String toString() {
return "GeoLocation{" +
"lng=" + lng +
", lat=" + lat +
'}';
}
}

although  GeoLocation  It only includes  lng  and  lat  Two attributes , There is a lot of code to implement a complete value object class .

As you can see from the code , The class of the value object follows a certain pattern , It can be derived from properties . This includes the method of obtaining properties 、equals  and  hashCode  Method 、 as well as  toString  Such method .

Common alternatives

In order to solve this cumbersome declaration problem , There are many alternatives .

The most common solution is to use Lombok Medium  @Value  annotation , This is shown in the following code .

import lombok.Value;

@Value
public class GeoLocation {

double lng;
double lat;
}

When used  javap  see Lombok The byte code actually generated , You can see Lombok Generated relevant methods .

Compiled from "GeoLocation.java"
public final class io.vividcode.java11to17.record.lombok.GeoLocation {
public io.vividcode.java11to17.record.lombok.GeoLocation(double, double);
public double getLng();
public double getLat();
public boolean equals(java.lang.Object);
public int hashCode();
public java.lang.String toString();
}

Kotlin There are data classes in (data class), The corresponding code is as follows .

data class GeoLocation(
val lng: Double,
val lat: Double
)

Scala There are also case class, The corresponding code is as follows .

case class GeoLocation(lng: Double, lat:Double)

Basic description of record type

The type of record is Java Language native value object implementation , By keywords  record  To express . Record type in Java 14 It is introduced in the form of preview function , stay Java 15 Preview again , stay Java 16 Become a formal function in .

The following code is used as a record type  GeoLocation. attribute  lng  and  lat  Components called record types . Each record type is an aggregate of the contained components . For record types , The compiler will automatically generate the components in the class .

public record GeoLocation(double lng, double lat) {

}
  • Each component will have a corresponding  private final  Field .

  • Constructor that assigns values to all components in the record . Because the record object is immutable , All properties need to be initialized in the constructor . The constructor's parameter declaration is exactly the same as the record type's component declaration .

  • For each component , Generate methods to get values . The name of the method is consistent with the name of the component , The return value type is consistent with the component type .

  • Automatically generated  equals  and  hashCode  Method , Perform equality comparison and calculate hash value according to components .

  • Automatically generated  toString  Method , Contains the value of each component .

It can also be used  javap  View the bytecode generated by the record type , As shown below . You can see ,GeoLocation  The above method is included in the byte code of .

Compiled from "GeoLocation.java"
public final class io.vividcode.java11to17.record.record.GeoLocation extends java.lang.Record {
public io.vividcode.java11to17.record.record.GeoLocation(double, double);
public final java.lang.String toString();
public final int hashCode();
public final boolean equals(java.lang.Object);
public double lng();
public double lat();
}

Record types are declared as  final  Of , That is, they can't be inherited .

The record type is a restricted type . This is the same as enumeration types . All record types are  java.lang.Record  Subclasses of .

The above is the basic introduction of record types . Here are some advanced usage .

Advanced usage of record types

If the value of the record component needs to be verified or normalized , You can add custom constructors . such as ,GeoLocation  The value of longitude and latitude in has a range limit . You can add validation logic to the constructor , This is shown in the following code .

public record GeoLocation(double lng, double lat) {

public GeoLocation(double lng, double lat) {
if (lng <= 180 && lng >= -180) {
this.lng = lng;
} else {
throw new IllegalArgumentException("Invalid value of longitude");
}
if (lat <= 90 && lat >= -90) {
this.lat = lat;
} else {
throw new IllegalArgumentException("Invalid value of latitude");
}
}
}

The added constructor is of the same type as the automatically generated constructor . under these circumstances , The compiler will no longer generate constructors .

This form of constructor needs to initialize all components . If only some components need to be processed , You can use a compact form of constructor . In the following code , For components only  isbn  It was verified . Other uninitialized components will be automatically added with assignment operation .

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

public Book {
if (isbn == null) {
throw new IllegalArgumentException("ISBN is invalid");
}
}
}

The above compact form of constructor , It's actually equivalent to the following code .

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

public Book(String isbn, String title, String description, BigDecimal price) {
if (isbn == null) {
throw new IllegalArgumentException("ISBN is invalid");
}
this.isbn = isbn;
this.title = title;
this.description = description;
this.price = price;
}
}

Record types can be nested , This makes them suitable for representing complex object structures . For example, in the following code  Order  type .

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

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

}

public record Address(String addressLine, String cityId, String provinceId,
String zipCode) {

}
}

Record types can appear inside methods , Call it a local record (local record). The local record type is suitable for Java Flow calculation related code . If the flow calculation is complex , Local records can be used to represent intermediate calculation results , So as to improve the readability of the code .

In the following code ,calculate  Method is used to calculate the maximum order amount of each user .OrderTotal  It's a local record , Represents the amount of each order . The first flow calculates the amount of each user's order , The second calculation gives the maximum amount .

public class OrderCalculator {

public Map<String, OrderSummary> calculate(List<Order> orders) {
record OrderTotal(String orderId, BigDecimal total) {

}

Map<String, List<OrderTotal>> 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);
return new 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()));
}
}

That's all for the record types .

Finally, push my new book 《Quarkus Cloud native microservice development practice 》, Interested friends are welcome to buy , Search Jingdong for the title of the book .

 picture

 Smart code
Smart code
The original of pure dry goods IT Technical articles , Gather the front and rear ends of the forefront 、 Mobile development and operation and maintenance technology
22 Original content
official account


版权声明
本文为[Smart code]所创,转载请带上原文链接,感谢
https://cdmana.com/2021/10/20211002145640952e.html

Scroll to Top