In Java, the concept of immutable refers to classes whose objects do not change information after they are instantiated.
Normally, to declare an immutable class, we would:
- Declare this class as final so that no class can extend from it.
- Declare the fields of that class as private and final,
- There are no Setter methods but only Getter methods,
- If one of the fields of that class is an object, then when we get the information of that field, we need to return a copy of that object.
For example, if I need to implement the Student class with 2 properties: name and age, which is an immutable class, I will declare it as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
package com.huongdanjava.javaexample; public final class Student { private final String name; private final int age; public Student(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; } } |
I have introduced to you Project Lombok, and its benefits in reducing the need to write code for Getter methods, or constructors, … when declaring a class. With the immutable Student class above, we can rewrite it with Project Lombox as follows:
1 2 3 4 5 6 7 8 9 10 11 12 |
package com.huongdanjava.javaexample; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor public final class Student { private final String name; private final int age; } |
From Java 14 onwards, we can do this much faster, much simpler, without using Project Lombok anymore, by using the record keyword when declaring the class. Now we only need 1 line of code to implement the immutable Student class above, as follows:
1 2 3 |
package com.huongdanjava.javaexample; public record Student(String name, int age) {} |
Java compiler will generate private, final fields, a public constructor with full fields (this constructor is also called canonical constructor), Getter methods for these fields, equals() method, hashCode() method, and method toString() method when we use record to declare a class. The objects of these classes will be immutable objects.
Now you can initialize and get information of a Student object as follows:
We cannot extend this Student class:
because this Student class is the final class.
You can declare other constructors for this Student class according to your needs, for example, default constructor or constructor with 1 parameter, as follows:
1 2 3 4 5 6 7 8 9 10 11 12 |
package com.huongdanjava.javaexample; public record Student(String name, int age) { public Student() { this("Khanh", 35); } public Student(String name) { this(name, 0); } } |
We can also declare static variables and static methods inside a record class:
1 2 3 4 5 6 7 8 9 10 |
package com.huongdanjava.javaexample; public record Student(String name, int age) { public static String NAME = "Huong Dan Java"; public static void doSomething() { System.out.println("Hello"); } } |
and use them, for example: