Java’s Records vs Kotlin’s Data Classes
How record classes in Java compares to Kotlin’s data classes.
Recent releases of Java have had some very noteworthy and developer-friendly features including a concept of record classes. From the Java docs of the Record
class:
A record class is a shallowly immutable, transparent carrier for a fixed set of values, called the record components. The Java language provides concise syntax for declaring record classes, whereby the record components are declared in the record header. The list of record components declared in the record header form the record descriptor.
My first thought on seeing this class and as someone who has used Kotlin for some time now, I couldn’t help but wonder how similar this sounds to Kotlin’s data classes.
So I did some exploring and here is my first impression comparing Java’s records to Kotlin’s data classes. But before that, let us take a quick look at what these are and how we can create one:
Records
public record Sample (String key, String value) {}
A record is a data holder that extends from Java’s base Record
class. A key feature of records classes are
- All properties are final and private.
- Declaring a class using the
record
key word generates theequals()
,hashcode()
andtoString()
methods hence reducing boiler plate code.
Data class
data class Sample (val key, val value)
Data classes are Kotlin’s data holder classes and some of its key features are:
- Properties can be final or not (declaring properties as a
var
makes them not final and hence they can be changed). - Similar to record classes, declaring a class as a data class automatically generates the
equals()
,hashcode()
andtoString()
methods.
With the basic definitions out of the way, let's talk about some key similarities and differences:
Similarities
Well, the first one that comes to mind is obviously how both these features remove the need for boilerplate code by automatically generating three methods: equals()
, hashcode()
and toString()
. Under the hood, how this code gets generated is different but from a high level to a developer, the code is automatically generated.
Differences
1: Data classes provide an additional copy()
method out of the box that allows you to selectively change property values of a class by just specifying a new value for that field, without having to copy over all the other values that don’t need a new value.
For example, let us create an object of the Sample
data class:
var sample1 = Sample("Key-1", "Value-1")
If we were to create another Sample object similar to sample1
but only change the value of the key field we can do it as follows:
val sample2 = sample1.copy(key = "Key-2")
This feature truly shines when you have multiple fields in your data class and you just have to change one of them. No such feature is available with Records.
2: Fields in a record class are ALL private and final, but with data classes, this is not always the case as it depends on how the fields are declared. For example, if we were to update the Sample
class to be as follows:
data class Sample(var key, var value)
We can change the values of the fields key
and value
as they are now a var
vs a val
.
3: There is no base data class instead the word data
is just a key word to indicate what type of class is being declared. With records, there is a base Record
class that is provided in the JDK that all classes that are declared as record
classes extend from. It is the base Record
class that provides the hashCode()
, toString()
and equals()
methods.
4: Data classes can extend other classes in Kotlin if those classes can be and are marked as open (making then inheritable). Note that all classes are final in Koltin by default unless they are marked as open
. But Java’s record classes cannot extend other classes. For example, the following code will work in Kotlin:
open class Parent
data class Sample(var key, var value): Parent()
On the other hand, the following will not compile in Java (assuming the Parent
class is
class Parent {}
public record SampleRecord(String key, String value) extends Parent// The above line will fail to compile with "No extends clause
// allowed for record"
5: The final and most significant difference (in my opinion) between Records and Data classes is the ability to define instance variables. In a data class we can do the following:
data class Sample(var key, var value) { var option: String? = ""}
And when we create an object of type Sample
we can choose to provide a value for the option
field. On the other hand record classes do not allow instance variables. So the following code will not compile:
public record SampleRecord(String key, String value) {
String hello = ""; // This line will not compile.
}
Conclusion
I hope that was a good sum-up of how Java’s record classes compare against Kotlin’s data classes! Record classes are definitely more immutable as compared to data classes whose immutability depends on how the class is declared. But the similarity between the two is a big win for all developers: automatic generation of boilerplate code!