Hey, Tea Lovers! Java 16 is released a few days back, well, a month to be precise. It introduced 17 new features. I discussed the 4 features that the developer will use a lot in my previous post, Simplified Java 16 Key Features. In this post, we will deep dive into Java 16 Record to see how it helps us to reduce the boilerplate code of POJO classes.
I would be happy to connect with you guys on social media. It’s @coderstea on Twitter, Linkedin, Facebook, Instagram, and YouTube.
Please Subscribe to the newsletter to know about the latest posts from CodersTea.
POJO Boilerplate Code
Whenever you need a simple class to hold the data, you write a POJO class, right? But Even though your need is simple, you need to write tonnes of code for just a few things. Such as getters and setters, constructors, toString, and equals and hashcode. No matter how many different POJOs you write, you have to write these 90% similar methods. These things with little to no changes that we have to write again and again for simple purposes are known as Boilerplate code.
public class Student {
private final String name;
private final int rollNo;
// all the below code is boilerplate code
public Student(String name, int rollNo) {
this.name = name;
this.rollNo = rollNo;
}
public String getName() {
return name;
}
public int getRollNo() {
return rollNo;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return rollNo == student.rollNo && name.equals(student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, rollNo);
}
}
Code language: PHP (php)
In the above example code, all the lines after line number 4 are boilerplate codes. Whatever class you create, only the fields will change and everything will remain the same. One other example of such a thing is JDBC connection initialization.
One solution to POJO Boiler Plate Code is Lombok
To overcome this POJO boilerplate code, there is a library called Lombok, which helps you to resolve this situation with Annotations. With this, your POJO only contains the fields that you need to hold the data and nothing more except a few Annotations. And I use this in every Java project I work on. You can learn more about it in my previous post, Say Good-Bye to Boilerplate Code with Lombok: Part 2-The Code.
@Data
class Student {
private final String name;
private final int rollNo;
// no boilrplate code, all those are getting generated on bytecode
// so it doesn't even clutter the code it self
}
Code language: PHP (php)
See? How does all that boilerplate code get reduced to a simple @Data
annotation? Cool right? But this is an external library. You have to add the dependency of Lombok in your project. And your IDE needs the plugin for it to work. (The latest versions of IntelliJ support the Lombok without any plugin now, which is a good thing).
However, starting Java 16, we won’t be needing it anymore. These additional things will be taken care of by the compiler itself with Java 16 Record. Let’s jump to it and see how. But before, prepare your cup of tea to sip and code.
The Things Java 16 Record Do for us
Before jumping into how to use Java 16 Record, let us first see what we are trying to solve here. As I already mentioned that you need to create a lot of boilerplate code to simply hold the data. With Java 16 Record you get all these things without any extra work. It generates all the boilerplate code we talked about. In addition to that, we don’t even need to declare the access modifier and final keyword. The things Java Record provides are,
- private and final immutable fields
- Constructor
- Getters
- equals()
- hashCode()
- toString()
We will now explore the Java 16 Record and will see how to use it. I discuss the above-mentioned list one by one.
How to Create a Java Record
Let us start with creating one Record. It is simple, you only require the name of the Record and the fields you need in that record. After defining parameters in the record, these fields will be private and immutable, meaning they will be private
and final
by default. I will take the same Student class of the earlier sample code and convert it to the Record.
public record Student (String name, int rollNo) {}
Code language: JavaScript (javascript)
Yes, that’s it. That simple record
keyword took care of all the boilerplate code we have to generate unnecessarily. And now, you can use it as you use the normal POJO class. Why don’t we now jump to the different parts of it, shall we?
The Constructor of Java 16 Record
By default, it gives us the public constructor with the exact parameter we used while declaring the record
. This is used the same way we used it in POJO. For example.
record Student (String name, int rollNo) {}
// another record
record Employee (String name, int id, List<String> skills) {}
Code language: JavaScript (javascript)
This will be called this,
Student student = new Student ("CodersTea", 1);
var anotherStudent = new Student("Dev Tools by CodersTea", 2);
var emp1 = new Employee("Imran", 1, List.of("nothing", "he knows nothing"))
Code language: PHP (php)
So, whatever fields you put in the creation will be your default public constructor. But what if you want to do something in the constructor? Such as validation? Let us see that in the next block.
Modify Default Constructor Initialization in Record
Let us say, in the Student record, we don’t want negative or zero as a value. And we don’t want the name to be null. And we want all of these to be validated in the existing or default constructor. For this, we need to do the following.
record Student (String name, int rollNo) {
public Student {
Objects.requireNonNull(name); // non null name
if ( rollNo < 1 ) { // not 0 or negative
throw new IllegalArgumentException("Roll No can not be 0 or negative");
}
}
}
Code language: JavaScript (javascript)
As you can see, I declared a constructor-like block without a parameter bracket and wrote the logic there. In this block, you can access the field directly with or without this
keyword.
Add Another Constructor in Java Record
Now, that we have seen how we can modify or do some extra login at the time of initialization of the default constructor, let us see how we can create another constructor.
Creating a new Constructor is a similar process to normal java classes. However, you must delegate it to the default constructor. By delegation, I mean you need to call the default constructor anyhow, either directly or via another constructor. Otherwise, it won’t compile.
For example, for the Employee record, If only name
and id
are important and skills
can be empty (like me 😭), then we can create another constructor with only name
and id
as parameters. Then in this new constructor, we will call the default constructor with these two parameters and pass an empty list as the 3rd param, skills
. Confusing? Ok, see the code.
public record Employee(String name, int id, List<String> skills) {
public Employee(String name, int id){
// delegating to default constructor
// with default skills
this(name, id, Collections.emptyList());
}
}
Code language: JavaScript (javascript)
You can then play around and have as many constructors as you want, but make sure they directly or indirectly call the default one. For example, if I want to have another constructor as the default id then I have two options after creating a new constructor with only the String name param. First, delegate to the constructor we created above, which has a default skills
defined. Or I can directly delegate to the default one by giving both skills
and id
default values.
public Employee (String name) {
// delegate to above constructor
this(name, 0);
// following will delegate to default constructor
// this(name, 0, Collections.emptyList();
}
Code language: JavaScript (javascript)
Getters in Java 16 Record
Getters generated by Record are not similar to our normal POJO getters. It doesn’t use get as a prefix instead the field name with (). So for our Students and Employee, it can be called like this.
// gives us the name fields value
String student1Name = student.name();
// skills field value
List empSkills = emp1.skills();
// rollNo of student2
int student2RollNo = anotherStudent.rollNo();
Code language: JavaScript (javascript)
toString Method in Java Record
By default the toString()
value of a class, like Abc, is Abc@69d0a921
. Which doesn’t seem to be the best output to understand its content of it. So we have to override the method and do string concatenation. In Record, it gets generated automatically. For example, printing the Student and Employee objects we created earlier will be printed as follows.
System.out.println(student);
System.out.println(emp1);
Code language: CSS (css)
The output would be like this,
Student[name=CodersTea, rollNo=1]
Employee[name=Imran, id=1, skills=[nothing, he knows nothing]]
Code language: CSS (css)
Equals and HashCode in Java 16 Record
To check whether or not two objects are the same, we use equals()
. And hashCode()
is something we need to override in case we create our own equals()
. In case equals()
If this is true, then both Objects must have the same hashCode(). The combination of these two methods is used in Set and Map. I have explained them in detail in How HashMap Works in Java, so please check it out.
These methods must be overridden carefully as they will hugely impact the output in case we are using them in Map or Set. With Record, we don’t need to worry about that as it takes care of these things automatically.
String name = "coderstea.in";
int rollNo = 123;
Student student1 = new Student(name, rollNo);
Student student2 = new Student(name, rollNo);
boolean equals = student1.equals(student2); // true
System.out.println("equals is " + equals);
boolean hashCode = student2.hashCode() == student1.hashCode(); // true
System.out.println("hashCode is " + hashCode);
Code language: JavaScript (javascript)
If and only if all the values in both the records are the same then only you get the same hashCode and true in equals by default. In the above example, if I create another Student object and use a different rollNo
then the result will be the opposite of the earlier one.
Student student3 = new Student(name, 100); // changes rollNo
student3.equals(student2); // false
student3.equals(student1); // false
var hc = student3.hashCode() == student1.hashCode(); // false
var hc1 = student3.hashCode() == student2.hashCode(); // false
Code language: JavaScript (javascript)
Static Members and Class in Java 16 Record
You can define the static variable and classes. Earlier, in the preview, the static members inside the inner static class of the Record were not allowed, but you can do so now starting Java 16. You can pretty much do all the things you do in a normal class.
record Employee(String name, int id, List skills) {
public static final float PI = 3.14f;
public static class InnerStaticClass{
public static final int WRONG_PI = 123;
}
}
Code language: PHP (php)
Inheritance in Java 16 Record
You can do inheritance in Java Record by implementing an interface, but can not extend other classes. The reason is that a record
internally converts to a class that extends Record.java, and as you know multiple inheritances are not allowed in Java, explained here.
interface DemoInterface {
void doSomething();
}
record Student(String name, int rollNo) implements DemoInterface{
@Override
public void doSomething() {
// do something
}
}
Code language: JavaScript (javascript)
Conclusion
That’s it for this post. I have covered the basics of Record of Java 16. It is just like a normal class but with special features. It will greatly improve productivity and reduce redundant code exponentially. No extra library of Lombok is to be set up in the project anymore.
Please do share this post if you think it has helped you understand the Java 16 Record. See you in the next post.
HAKUNA MATATA!!!
I would be happy to connect with you guys on social media. It’s @coderstea on Twitter, Linkedin, Facebook, Instagram, and YouTube.
Please Subscribe to the newsletter to know about the latest posts from CodersTea.