CodeX
Published in

CodeX

Are you really taking advantage of Kotlin’s Data classes?

Data classes are used extensively in Kotlin because of their features that facilitate handling data. But, in my experience, it’s often the case that developers don’t know the extent of the features data classes offers, which ends up limiting their use. This article dives into the powers and uses cases of data classes to help you evaluate when and how you should use them. Let’s get to it!

image by qimono

Basic definition and usage

The motivation behind data classes is to offer an easy way to handle classes whose main purpose is to hold data. In other words, they include behavior that we often need when handling data without the need to explicitly write the extra logic. They do so by encapsulating boilerplate code from Java that defines the behavior, for example, the following Java code:

can be replaced by this Kotlin one liner:

But data classes implement more robust behaviors than simply default getter and setters, and we look at each of these features below.

Representing data class objects as String

Like any Kotlin class, data classes subclass the Any base class, which provides a toString() function that allows us to represent any object as a String. By default, toString() prints the object’s class name along with its hash code. This is not quite useful for data containers, after all when using data classes, we are usually interested in the object’s data. To implement a more useful behavior, data class overrides toString() to print the current value of the object’s properties as follows:

Keep in mind that if we had more properties, toString would adapt to include all properties defined inside the primary constructor, but would not include properties defined in the class body. For example:

Comparing Objects

Another function available from the Any base class is the equals function, used to compare object equality. By default, equals compares objects by reference, meaning that two objects are equal if and only if they reference the same object. For instance:

Comparing objects’ references is not quite useful when dealing with data holding classes. In this case, it makes more sense to compare objects by the value of their properties instead, and this is exactly the behavior that data classes implement. They override their equals function to output true when the comparison between all the properties defined in their primary constructor return true. For instance:

Keep in mind that when comparing the properties, the property’s equals function is called, justifying that the Strings above are compared by their actual values and the A instances by reference since they are not data classes.

What about hash codes? Hash codes are used to uniquely identify objects. For example, when an object is used as a key in a HashMap, their hash code is used as the key. So it only makes sense that when two objects are equal, based on the output of the equals function, they should also have the same hash code. Indeed, data classes take care of this for you to make sure that hash codes stay consistent. For example, variables doe and jane above share the same hash code.

Destructuring Properties

Kotlin offers a convenient way to initialize variables to hold multiple properties of certain objects at once. This is called destructuring and can be used in a variety of situations, from Collections to lambda parameters.

Unsurprisingly, data classes support this restructuring for its primary properties as well. In fact, Pair and Triple, in the example above, are classes in the standard library implemented using data classes behind the scenes.

Copying Objects

You may have noticed that in the examples above we have used both val and var properties in the constructor of data classes. However, it is often considered bad practice to expose var properties like that because you lose control of how other pieces of your application are changing the value of the property. More specifically, this could cause a race condition if two pieces of code try to change the property simultaneously.

To avoid that, it’s common practice to only define val properties in the primary constructor. But that raises the question: How can we still change that variable easily?

To solve this, data classes implement a copy method that allows you to modify some or all primary properties of a data class object and reassign it to the same object or a different one.

Limitations

Data classes offer quite a few handy tools for working with classes that manage data and should be used when you have a simple class whose main purpose is holding data. However, they also enforce a few noteworthy limitations:

  • Their primary constructor must be declared with at least one parameter;
  • Each of their primary constructor parameters must be defined as a val or var. You can still include access modifiers like private though;
  • They cannot be abstract, open, sealed, or inner.

References

This article includes some information directly from the docs, where you can also find some additional helpful information about data classes.

Thank you for reading! If you enjoy the content, don’t hesitate to leave a clap and follow for more!

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Joao Foltran

Joao Foltran

Android Engineer and avid runner