Java Records – A Beginner’s Guide

栏目: IT技术 · 发布时间: 5年前

内容简介: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
Last modified:

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:

Java Records – A Beginner’s Guide

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.

Java Records – A Beginner’s Guide

If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.

Java Records – A Beginner’s Guide Java Records – A Beginner’s Guide

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.


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

图解Java多线程设计模式

图解Java多线程设计模式

[日] 结城浩 / 侯振龙、杨文轩 / 人民邮电出版社 / 2017-8 / 89.00元

本书通过具体的Java 程序,以浅显易懂的语言逐一说明了多线程和并发处理中常用的12 种设计模式。内容涉及线程的基础知识、线程的启动与终止、线程间的互斥处理与协作、线程的有效应用、线程的数量管理以及性能优化的注意事项等。此外,还介绍了一些多线程编程时容易出现的失误,以及多线程程序的阅读技巧等。在讲解过程中,不仅以图配文,理论结合实例,而且提供了运用模式解决具体问题的练习题和答案,帮助读者加深对多线......一起来看看 《图解Java多线程设计模式》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换