Week 14 — What are records in Java

Question of the Week #14
What are records in Java
12 Replies
Eric McIntyre
Eric McIntyre2y ago
java records are immutable classes for example we have a class with fields that fields are final
final class Example { final Integer first = null;final Integer secound = null}
final class Example { final Integer first = null;final Integer secound = null}
in java 14 we can make it with record
Record Example {Integer first = null; Integer secound= null; }
Record Example {Integer first = null; Integer secound= null; }
Eric McIntyre
Eric McIntyre2y ago
and we dont even need to do final
Submission from melon_#4695
Eric McIntyre
Eric McIntyre2y ago
Java 16 introduced a new feature called "records", which is a type of class that is specifically designed for representing data. Records provide a concise and powerful way to define classes that only store data and don't have any behavior of their own. In this post, we'll take a closer look at what records are, how they work, and how you can use them in your Java projects. At a high level, a record in Java is just a special kind of class that is designed to represent data. Like a regular class, a record can have fields (i.e. variables) that store data. However, unlike a regular class, a record doesn't have any methods or behavior of its own. Instead, the fields are the primary focus of a record, and the class is optimized for storing and accessing those fields. Here's an example of a record class that represents a person's name:

public record PersonName(String firstName, String lastName) {}

public record PersonName(String firstName, String lastName) {}
This record defines two fields: firstName and lastName. It also has a constructor that takes those two fields as arguments, and automatically generates methods for accessing those fields. Under the hood, records are actually implemented as regular classes with a few special features. When you define a record, the compiler automatically generates several methods for you: - A constructor that takes all of the record's fields as arguments. - Getter methods for each field. - equals() and hashCode() methods that compare two records based on their fields. - A toString() method that returns a string representation of the record. These methods are all generated automatically based on the fields that you define in your record. This means that you don't need to write any boilerplate code to create a record class - you can just define the fields you need and let the compiler take care of the rest.
Eric McIntyre
Eric McIntyre2y ago
Records are a powerful new feature in Java that provide a concise and elegant way to represent data classes. By defining a record, you can create a class that is optimized for storing and accessing fields, without having to write any boilerplate code. If you're working on a project that involves a lot of data classes, be sure to check out records in Java 14!
Submission from Jeong#3152
Eric McIntyre
Eric McIntyre2y ago
Within Java, records were created to reduce the amount of boilerplate code that comes along when creating a class. Along with that, a majority of the time, when a class is created, the class is purely made with the intention to hold immutable data. Example: Let's create a class called Student
import java.util.Objects;

public class Student {

// Class Variables
private final String ID;
private final String Initials;
private final String Surname;

// Constructor
public Student(String ID, String initials, String surname) {
this.ID = ID;
this.Initials = initials;
this.Surname = surname;
}

// Accessor Methods
// ommitted for brevity

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return this.ID.equals(student.ID) && this.Initials.equals(student.Initials) && this.Surname.equals(student.Surname);
}

@Override
public int hashCode() {
return Objects.hash(this.ID, this.Initials, this.Surname);
}

@Override
public String toString() {
return "Student{" +
"ID='" + this.ID + '\'' +
", Initials='" + this.Initials + '\'' +
", Surname='" + this.Surname + '\'' +
'}';
}
}
import java.util.Objects;

public class Student {

// Class Variables
private final String ID;
private final String Initials;
private final String Surname;

// Constructor
public Student(String ID, String initials, String surname) {
this.ID = ID;
this.Initials = initials;
this.Surname = surname;
}

// Accessor Methods
// ommitted for brevity

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return this.ID.equals(student.ID) && this.Initials.equals(student.Initials) && this.Surname.equals(student.Surname);
}

@Override
public int hashCode() {
return Objects.hash(this.ID, this.Initials, this.Surname);
}

@Override
public String toString() {
return "Student{" +
"ID='" + this.ID + '\'' +
", Initials='" + this.Initials + '\'' +
", Surname='" + this.Surname + '\'' +
'}';
}
}
From this, we see a lot of boiler plate code that needs to be added for the class. Although modern IDEs allow for easy creation of this all, it is still tedious to do each time. Moreover, if we were to go and add in Student email, everything will have to be updated to account for this change. None of it adjusts itself automatically according to the small change you made when compiled. That is where Records come in. As with the definition stated at the beginning of this response, a record allows a user to skip all of the tedious boilerplate management and allows for easy adjusting of code (that is, adding in a new field to store will automatically do the necessary changes to boilerplate code when compiled). As a result, the programmer is able to focus more on the creating of new records to store the data. After all, is that not the really point of a class? Below would be the result of turning the Student class into a record:
public record Student(String ID, String Initials, String Surname) {}
public record Student(String ID, String Initials, String Surname) {}
As simple as this. Moreover, a record allows for different constructors to be used: Compact, Canonical, Custom version or None (would call the default constructor) Canonical.
public record Student(String ID, String Initials, String Surname) {
public Student(String ID, String Initials, String Surname) {
this.ID = ID;
this.Initials = Initials;
this.Surname = Surname;
}
}
public record Student(String ID, String Initials, String Surname) {
public Student(String ID, String Initials, String Surname) {
this.ID = ID;
this.Initials = Initials;
this.Surname = Surname;
}
}
Compact:
public record Student(String ID, String Initials, String Surname) {
public Student() {
}
}
public record Student(String ID, String Initials, String Surname) {
public Student() {
}
}
Custom:
public record Student(String ID, String Initials, String Surname) {
public Student(String surname) {
this("1726374854", "MD", surname);
}
}
public record Student(String ID, String Initials, String Surname) {
public Student(String surname) {
this("1726374854", "MD", surname);
}
}
Eric McIntyre
Eric McIntyre2y ago
Worth noting that a record can only have one constructor. If you wanted to have a compact and custom constructor, a compilation error will occur. If the programmer were to now add in the email for the Student, everything would be adjusted for the new field being stored when compiled. Even better is the fact that records are able to support the following: 1) being serializable 2) generics 3) implement interfaces 4) contain nested classes and interfaces 5) be nested inside other records
⭐ Submission from MilaDog#1234
Eric McIntyre
Eric McIntyre2y ago
They are a form of abstract data type. Essentially they are classes, they work like classes but classes that have only a set of variables in them (or just classes that only hold attributes (If you would like to be specific they are called record fields not "attributes")):
// This is an example of a record that holds an Attribute NUM of type int.
class Example{
int NUM;
}
// This is an example of a record that holds an Attribute NUM of type int.
class Example{
int NUM;
}
They can contain a combination of attributes with varying types:
// This is a record that holds a String and also an int.
class Example2{
int NUM;
String TXT;
}
// This is a record that holds a String and also an int.
class Example2{
int NUM;
String TXT;
}
So far you can see how these then become ADTs (Abstract Data Types). Like a class, a record can have multiple instances of it created. Lets say you want to hold two instances of Example2:
class AccessADT{
public static void main(String[] args){

// We declare a new instance of Type Exmaple2.
Example2 a = new Example2();
// We declare another new instance of Type Example2, they are not necessarily identical...
Example2 b = new Example2();

return;
}
}
class AccessADT{
public static void main(String[] args){

// We declare a new instance of Type Exmaple2.
Example2 a = new Example2();
// We declare another new instance of Type Example2, they are not necessarily identical...
Example2 b = new Example2();

return;
}
}
Eric McIntyre
Eric McIntyre2y ago
It is good practice to never directly access these using dot functions like the following:
// The "bad" way of accessing or changing values in a record. Using above as reference:
System.out.println(a.NUM);
// Or changing it like this:
a.NUM = 1;
// The "bad" way of accessing or changing values in a record. Using above as reference:
System.out.println(a.NUM);
// Or changing it like this:
a.NUM = 1;
Instead you should define get and set methods for these record fields which makes for a better decomposed and defensive style of programming:
class AccessADT{
public static void main(String[] args){

// We declare a new instance of Type Exmaple2.
Example2 a = new Example2();
// We declare another new instance of Type Example2, they are not necessarily identical...
Example2 b = new Example2();

return;
}

// An example of a get method.
public static String getTXT(Example2 obj){
return obj.TXT;
}

// An example of a set method.
public static void setTXT(Example2 obj, String setString){
obj.TXT = setString;
return;
}
}
class AccessADT{
public static void main(String[] args){

// We declare a new instance of Type Exmaple2.
Example2 a = new Example2();
// We declare another new instance of Type Example2, they are not necessarily identical...
Example2 b = new Example2();

return;
}

// An example of a get method.
public static String getTXT(Example2 obj){
return obj.TXT;
}

// An example of a set method.
public static void setTXT(Example2 obj, String setString){
obj.TXT = setString;
return;
}
}
If you want a default value in your record's fields, you can also freely initialize that alongside the declaration of that field:
class Example3{
// Setting the default value to always be 3.
int NUM = 3;
}
class Example3{
// Setting the default value to always be 3.
int NUM = 3;
}
Now of course you can also use records to hold other records and all sorts of stuff like that which is why they are very helpful when you want to work with a set of related data in your program which don't necessarily share the same "type". A very helpful usage of records I happen to use often is to move multiple related data in and out of methods when I'm programming. Say I want a set of Strings and Ints to be manipulated and returned to me via one method, I can just make a record to hold that data and return the record instead and work with it that way.
⭐ Submission from Fading#1337
Eric McIntyre
Eric McIntyre2y ago
Records are Modern Java's Response to the growing demand for Data Classes as First Class Feature of the Language. Before Records you'd often see something like the following, in combination with Lombok.
@Getter
@RequiredArgsConstructor
public class MyDataClass {
private final Object myObj;
private final int myInt;
private final String myStr;
}
@Getter
@RequiredArgsConstructor
public class MyDataClass {
private final Object myObj;
private final int myInt;
private final String myStr;
}
This was the simplest way to create an Object with immutable fields and getters. There are issues to this approach, namely the usage of an external library, and the fact you still had to use private final. Without Lombok you'd have to write all the code yourself, and override the toEquals() for a data class can be error prone. Overall this is a lot of boilerplate for a data bucket. Java Records were created to simply this process and give the Data Class a First Class approach in Java. Our above MyDataClass turns into the following.
public record MyDataClass(Object myObj, int myInt, String myStr) {}
public record MyDataClass(Object myObj, int myInt, String myStr) {}
This example helps us understand better what record is for Java. It's an evolution of the Language targeting how to represent immutable data classes. These types of classes are preferred for things like Functional Programming, Data Transfer Objects, etc. All parameters in the declaration are considered private final, and are given getters. They also cannot be extended, which helps prevent losing where Data is declared, a common problem with Polymorphic POJOs.
Submission from LeanusCrain#5877
Eric McIntyre
Eric McIntyre2y ago
Records are an easy way to create data classes, which hold several values in one, immutable class. Records have one predefined constructor, with the arguments provided in the record definition, which fills the record. All record fields are final, and have getters. Other than that, a record class behaves mostly the same as any other class, with the exception of it not being able to have nonfinal, nonstatic fields. Records have some predefined methods as well: toString(): Default format of Person[name=0x150, age=22] hashCode(): Delegates to elements of record by default
record Person(String name, int age) {
public boolean canVote() {
return age >= 18;
}
public void thisWillNotCompile() {
this.age++; // not allowed: record fields (including age) are always final
}
}
Person person = new Person("0x150", 22);
System.out.println(person.canVote());
System.out.println(person.name()); // getter for name
record Person(String name, int age) {
public boolean canVote() {
return age >= 18;
}
public void thisWillNotCompile() {
this.age++; // not allowed: record fields (including age) are always final
}
}
Person person = new Person("0x150", 22);
System.out.println(person.canVote());
System.out.println(person.name()); // getter for name
Submission from 0x150#9699
Eric McIntyre
Eric McIntyre2y ago
Records are special types of classes which hold immutable data. It is possible to create records using the record keyword and adding record components (variables storing the data) in parenthesis:
record SomeRecord(String s, int i){}
record SomeRecord(String s, int i){}
These variables are always final and it is not possible to introduce other (non-static) fields inside the record. Records always extend java.lang.Record and it is not possible to extend other classes. However, it is certainly possible to implement interfaces. When creating a record, Java automatically provides getters with the same names as the variables and matching equals()/hashCode()/toString methods and an all-args constructor. The above record would roughly be equivalent to
final class SomeRecord{
private final String s;
private final int i;
public SomeRecord(String s, int i){
this.s=s;
this.i=i;
}
public String s(){
return s;
}
public int i(){
return i;
}
@Override
public boolean equals(Object o){
return o instanceof SomeRecord other && Objects.equals(this.s, other.s) && this.i == other.i;
}
@Override
public int hashCode(){
return Objects.hash(s,i);
}
@Override
public String toString(){
return "SomeRecord[s="+s+", i="+i+"]";
}
}
final class SomeRecord{
private final String s;
private final int i;
public SomeRecord(String s, int i){
this.s=s;
this.i=i;
}
public String s(){
return s;
}
public int i(){
return i;
}
@Override
public boolean equals(Object o){
return o instanceof SomeRecord other && Objects.equals(this.s, other.s) && this.i == other.i;
}
@Override
public int hashCode(){
return Objects.hash(s,i);
}
@Override
public String toString(){
return "SomeRecord[s="+s+", i="+i+"]";
}
}
Eric McIntyre
Eric McIntyre2y ago
It is possible to create additional constructors and change the existing all-args constructor (which is also called canonical constructor) for records. The canonical constructor will always exist, even when creating other constructors
record SomeRecord(String s, int i){
public SomeRecord(){
this("",0);
}
public SomeRecord(String s, int i){//it is possible to explicitely define a canonical constructor
this.s=s;
this.i=i;
}
}
record SomeRecord(String s, int i){
public SomeRecord(){
this("",0);
}
public SomeRecord(String s, int i){//it is possible to explicitely define a canonical constructor
this.s=s;
this.i=i;
}
}
It is possible to abbreviate the canonical constructor by omitting parameter list (including omitting the parenthesis) the variable initializations.
record SomeRecord(String s, int i){
public SomeRecord{//compact constructor
//additional logic
}
}
record SomeRecord(String s, int i){
public SomeRecord{//compact constructor
//additional logic
}
}
This is useful for custom validation logic:
record SomeRecord(String s, int i){
public SomeRecord{//compact constructor
Objects.requireNonNull(s);
if(i<0){
throw new IllegalArgumentException("i cannot be null");
}
}
}
record SomeRecord(String s, int i){
public SomeRecord{//compact constructor
Objects.requireNonNull(s);
if(i<0){
throw new IllegalArgumentException("i cannot be null");
}
}
}
⭐ Submission from dan1st#7327
Want results from more Discord servers?
Add your server