内容简介:In this article, I’m going to present to you the Records feature that was introduced in Java 14, and which aims to simplify the way we create a POJO (Plain Old Java Objects), DTO, or Value Object.Let’s assume we have the following
Imagine having a tool that can automatically detect if you are using JPA and Hibernate properly. Hypersistence Optimizer is that tool!
Introduction
In this article, I’m going to present to you the Records feature that was introduced in Java 14, and which aims to simplify the way we create a POJO (Plain Old Java Objects), DTO, or Value Object.
Domain Model
Let’s assume we have the following PostInfo
and AuditInfo
POJO classes:
Both classes define several properties and provide specific implementations for the equals
, hashCode
, and toString
Java Object
methods.
The AuditInfo
class is implemented like this:
public class AuditInfo { private final LocalDateTime createdOn; private final String createdBy; private final LocalDateTime updatedOn; private final String updatedBy; public AuditInfo( LocalDateTime createdOn, String createdBy, LocalDateTime updatedOn, String updatedBy) { this.createdOn = createdOn; this.createdBy = createdBy; this.updatedOn = updatedOn; this.updatedBy = updatedBy; } public LocalDateTime getCreatedOn() { return createdOn; } public String getCreatedBy() { return createdBy; } public LocalDateTime getUpdatedOn() { return updatedOn; } public String getUpdatedBy() { return updatedBy; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof AuditInfo)) return false; AuditInfo auditInfo = (AuditInfo) o; return createdOn.equals(auditInfo.createdOn) && createdBy.equals(auditInfo.createdBy) && Objects.equals(updatedOn, auditInfo.updatedOn) && Objects.equals(updatedBy, auditInfo.updatedBy); } @Override public int hashCode() { return Objects.hash( createdOn, createdBy, updatedOn, updatedBy ); } @Override public String toString() { return String.format(""" AuditInfo { createdOn : '%s', createdBy : '%s', updatedOn : '%s', updatedBy : '%s' } """, createdOn, createdBy, updatedOn, updatedBy ); } }
And the PostInfo
class looks as follows:
public class PostInfo { private final Long id; private final String title; private final AuditInfo auditInfo; public PostInfo( Long id, String title, AuditInfo auditInfo) { this.id = id; this.title = title; this.auditInfo = auditInfo; } public Long getId() { return id; } public String getTitle() { return title; } public AuditInfo getAuditInfo() { return auditInfo; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof PostInfo)) return false; PostInfo postInfo = (PostInfo) o; return id.equals(postInfo.id) && title.equals(postInfo.title) && auditInfo.equals(postInfo.auditInfo); } @Override public int hashCode() { return Objects.hash( id, title, auditInfo ); } @Override public String toString() { return String.format(""" PostInfo { id : '%s', title : '%s', auditInfo : { createdOn : '%s', createdBy : '%s', updatedOn : '%s', updatedBy : '%s' } } """, id, title, auditInfo.createdOn, auditInfo.createdBy, auditInfo.updatedOn, auditInfo.updatedBy ); } }
Frankly, that’s a lot of code for such a simple data object.
Java Records
Java 14 introduces a new way of defining such data objects, as Records, that take the burden of defining the fields, getters, equals
, hashCode
, and toString
method implementations.
So, let’s see how the AuditInfo
and PostInfo
classes look when we define them as Records, instead of Plain Old Java Objects:
public record AuditInfo( LocalDateTime createdOn, String createdBy, LocalDateTime updatedOn, String updatedBy ) {} public record PostInfo( Long id, String title, AuditInfo auditInfo ) {}
That’s it!
Behind the scenes, Java Records are defined as any other Java class. In our case, the decompiled classes look as follows:
public final class PostInfo extends java.lang.Record { private final java.lang.Long id; private final java.lang.String title; private final AuditInfo auditInfo; public PostInfo( java.lang.Long id, java.lang.String title, AuditInfo auditInfo) { /* compiled code */ } public java.lang.String toString() { /* compiled code */ } public final int hashCode() { /* compiled code */ } public final boolean equals(java.lang.Object o) { /* compiled code */ } public java.lang.Long id() { /* compiled code */ } public java.lang.String title() { /* compiled code */ } public AuditInfo auditInfo() { /* compiled code */ } } public final class AuditInfo extends java.lang.Record { private final java.time.LocalDateTime createdOn; private final java.lang.String createdBy; private final java.time.LocalDateTime updatedOn; private final java.lang.String updatedBy; public AuditInfo( java.time.LocalDateTime createdOn, java.lang.String createdBy, java.time.LocalDateTime updatedOn, java.lang.String updatedBy) { /* compiled code */ } public java.lang.String toString() { /* compiled code */ } public final int hashCode() { /* compiled code */ } public final boolean equals(java.lang.Object o) { /* compiled code */ } public java.time.LocalDateTime createdOn() { /* compiled code */ } public java.lang.String createdBy() { /* compiled code */ } public java.time.LocalDateTime updatedOn() { /* compiled code */ } public java.lang.String updatedBy() { /* compiled code */ } }
The generated class is final
and extends the Record
base class that was introduced by Java 14.
Since Java Records define a single constructor that takes the same arguments we used when defining the Record type, this is how we can create a PostInfo
with an AuditInfo
object:
PostInfo postInfo = new PostInfo( 1L, "High-Performance Java Persistence", new AuditInfo( LocalDateTime.of(2016, 11, 2, 12, 0, 0), "Vlad Mihalcea", LocalDateTime.now(), "Vlad Mihalcea" ) );
Note that, unlike the POJO specification, Java Records getters don’t follow the Java Bean standard, and the method names match the encapsulated field names:
assertEquals( 1L, postInfo.id().longValue() ); assertEquals( "High-Performance Java Persistence", postInfo.title() ); assertEquals( LocalDateTime.of(2016, 11, 2, 12, 0, 0), postInfo.auditInfo().createdOn() ); assertEquals( "Vlad Mihalcea", postInfo.auditInfo().createdBy() );
We can see that a toString
method is also generated, and the implementation is based on the Record properties. So, when calling the toString
methods of the AuditInfo
and PostInfo
Records:
LOGGER.info("Audit info:\n{}", postInfo.auditInfo()); LOGGER.info("Post info:\n{}", postInfo);
We get the following log entries:
Audit info: AuditInfo[createdOn=2016-11-02T12:00, createdBy=Vlad Mihalcea, updatedOn=2020-04-14T12:29:29.534875700, updatedBy=Vlad Mihalcea] Post info: PostInfo[id=1, title=High-Performance Java Persistence, auditInfo=AuditInfo[createdOn=2016-11-02T12:00, createdBy=Vlad Mihalcea, updatedOn=2020-04-14T12:29:29.534875700, updatedBy=Vlad Mihalcea]]
Customizing Java Records
Even if the generated classes are final
, we can still override the default methods. For instance, let’s say we want to provide a custom toString
implementation that matches the one we defined previously in our POJO classes.
To override the toString
method, we just have to provide the new method definition when declaring Java Records:
public record AuditInfo( LocalDateTime createdOn, String createdBy, LocalDateTime updatedOn, String updatedBy ) { @Override public String toString() { return String.format(""" AuditInfo { createdOn : '%s', createdBy : '%s', updatedOn : '%s', updatedBy : '%s' } """, createdOn, createdBy, updatedOn, updatedBy ); } } public record PostInfo( Long id, String title, AuditInfo auditInfo ) { @Override public String toString() { return String.format(""" PostInfo { id : '%s', title : '%s', auditInfo : { createdOn : '%s', createdBy : '%s', updatedOn : '%s', updatedBy : '%s' } } """, id, title, auditInfo.createdOn, auditInfo.createdBy, auditInfo.updatedOn, auditInfo.updatedBy ); } }
Now, when the Logger framework calls the toString
method, this is what we get in the application log:
Audit info: AuditInfo { createdOn : '2016-11-02T12:00', createdBy : 'Vlad Mihalcea', updatedOn : '2020-04-14T12:45:09.569632400', updatedBy : 'Vlad Mihalcea' } Post info: PostInfo { id : '1', title : 'High-Performance Java Persistence', auditInfo : { createdOn : '2016-11-02T12:00', createdBy : 'Vlad Mihalcea', updatedOn : '2020-04-14T12:45:09.569632400', updatedBy : 'Vlad Mihalcea' } }
Cool, right?
I'm running an online workshop on the 14th of May about The Best Way to Fetch Data with Java Persistence and Hibernate.
If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.
Conclusion
The new Java Records feature is very handy, as it simplifies the way we build value objects. Just like Multiline String Text Blocks , this is a preview language feature in Java 14.
So, if you want to try it out, not that you need to use the enable-preview
to both the Java compiler and the JVM when running the program.
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。