When is a class a data class?

Viewed 767

I know what classes are about, but for better understanding I need a use case. Recently I discovered the construct of data classes. I get the idea behind normal classes, but I cannot imagine a real use case for data classes.

When should I use a data class and when I use a "normal" class? For all I know, all classes keep data.

Can you provide a good example that distinguishes data classes from non-data classes?

3 Answers

A data class is used to store data. It's lighter than a normal class, and can be compared to an array with key/value (dictionary, hash, etc.), but represented as an object with fixed attributes. In kotlin, according to the documentation, that adds those attributes to the class:

equals()/hashCode() pair

toString() of the form "User(name=John, age=42)"

componentN() functions corresponding to the properties in their order of declaration.

copy() function

Also it has a different behavior during class inheritence :

If there are explicit implementations of equals(), hashCode(), or toString() in the data class body or final implementations in a superclass, then these functions are not generated, and the existing implementations are used.

If a supertype has componentN() functions that are open and return compatible types, the corresponding functions are generated for the data class and override those of the supertype. If the functions of the supertype cannot be overridden due to incompatible signatures or due to their being final, an error is reported.

Providing explicit implementations for the componentN() and copy() functions is not allowed.

So in kotlin, if you want to describe an object (a data) then you may use a dataclass, but if you're creating a complex application and your class needs to have special behavior in the constructor, with inheritence or abstraction, then you should use a normal class.


I do not know Kotlin, but in Python, a dataclass can be seen as a structured dict. When you want to use a dict to store an object which has always the same attributes, then you should not put it in a dict but use a Dataclass.

The advantage with a normal class is that you don't need to declare the __init__ method, as it is "automatic" (inherited).

Example :

This is a normal class

class Apple:
    def __init__(size:int, color:str, sweet:bool=True):
        self.size = size
        self.color = color
        self.sweet = sweet

Same class as a dataclass

from dataclasses import dataclass

@dataclass
class Apple:
    size: int
    color: str
    sweet: bool = True

Then the advantage compared to a dict is that you are sure of what attribute it has. Also it can contains methods.

The advantage over to a normal class is that it is simpler to declare and make the code lighter. We can see that the attributes keywords (e.g size) are repeated 3 times in a normal class, but appear only once in a dataclass.

The advantage of normal class also is that you can personalize the __init__ method, (in a dataclass also, but then you lose it's main advantage I think) example:

# You need only 2 variable to initialize your class
class Apple:
    def __init__(size:int, color:str):
        self.size = size
        self.color = color
        # But you get much more info from those two.
        self.sweet = True if color == 'red' else False
        self.weight = self.__compute_weight()
        self.price  = self.weight * PRICE_PER_GRAM

   def __compute_weight(self):
        # ...
        return (self.size**2)*10 # That's a random example

Abstractly, a data class is a pure, inert information record that doesn’t require any special handling when copied or passed around, and it represents nothing more than what is contained in its fields; it has no identity of its own. A typical example is a point in 3D space:

data class Point3D(
    val x: Double,
    val y: Double,
    val z: Double
)

As long as the values are valid, an instance of a data class is entirely interchangeable with its fields, and it can be put apart or rematerialized at will. Often there is even little use for encapsulation: users of the data class can just access the instance’s fields directly. The Kotlin language provides a number of convenience features when data classes are declared as such in your code, which are described in the documentation. Those are useful when for example building more complex data structures employing data classes: you can for example have a hashmap assign values to particular points in space, and then be able to look up the value using a newly-constructed Point3D.

val map = HashMap<Point3D, String>()
map.set(Point3D(3, 4, 5), "point of interest")
println(map.get(Point3D(3, 4, 5))) // prints "point of interest"

For an example of a class that is not a data class, take FileReader. Underneath, this class probably keeps some kind of file handle in a private field, which you can assume to be an integer (as it actually is on at least some platforms). But you cannot expect to store this integer in a database, have another process read that same integer from the database, reconstruct a FileReader from it and expect it to work. Passing file handles between processes requires more ceremony than that, if it is even possible on a given platform. That property makes FileReader not a data class. Many examples of non-data classes will be of this kind: any class whose instances represent transient, local resources like a network connection, a position within a file or a running process, cannot be a data class. Likewise, any class where different instances should not be considered equal even if they contain the same information is not a data class either.

From the comments, it sounds like your question is really about why non-data classes exist in Kotlin and why you would ever choose not to make a data class. Here are some reasons.

Data classes are a lot more restrictive than a regular class:

  • They have to have a primary constructor, and every parameter of the primary constructor has to be a property.
  • They cannot have an empty constructor.
  • They cannot be open so they cannot be subclassed.

Here are other reasons:

  • Sometimes you don't want a class to have a copy function. If a class holds onto some heavy state that is expensive to copy, maybe it shouldn't advertise that it should be copied by presenting a copy function.
  • Sometimes you want to use an instance of a class in a Set or as Map keys without two different instances being considered as equivalent just because their properties have the same values.

The features of data classes are useful specifically for simple data holders, so the drawbacks are often something you want to avoid.

Related