The Programmers Guide To Kotlin - Data Classes
Written by Mike James   
Monday, 09 October 2017
Article Index
The Programmers Guide To Kotlin - Data Classes
Equals, Destructuring

Kotlin doesn't have structs or records specifically designed for storing data as you might find in other languages but it does have a special type of class that it just right for the job. 

 

Programmer's Guide To Kotlin Third Edition

kotlin3e360

You can buy it from: Amazon

Contents

  1. What makes Kotlin Special
  2. The Basics:Variables,Primitive Types and Functions 
  3. Control
         Extract: If and When 
  4. Strings and Arrays
  5. The Class & The Object ***NEW!
  6. Inheritance
  7. The Type Hierarchy
  8. Generics
  9. Collections, Iterators, Sequences & Ranges
        Extract: Iterators & Sequences 
  10. Advanced functions 
  11. Anonymous, Lamdas & Inline Functions
  12. Data classes, enums and destructuring
        Extract: Destructuring 
  13. Exceptions, Annotations & Reflection
  14. Coroutines
        Extract: Coroutines 
  15. Working with Java
        Extract: Using Swing
  16. Compose Multiplatform
        Extract: Compose Layout 

<ASIN:B0D8H4N8SK>

 

This is an extract from the chapter about the new features that Kotlin supports to make working with data within a program easier.

Data Classes

If you program in a language other than Java or Kotlin, you may be familiar with structs, structure or records to store data, sometimes along with a few simple methods.

In Java and hence Kotlin there are no structs. Instead, to store data you simply create a class that has the properties corresponding to the data you want to store. For example:

class MyPersonClass{
  var firstName:String=""
  var secondName:String=""
  var age:Int=0
}

This is a simple class that can be used to store a person’s name and age.

For example:

val person1=MyPersonClass()
    person1.firstName="Mickey"
    person1.secondName="Mouse"
    person1.age=89

Other languages permit the use of classes in exactly this way so why do they have things like structs?

The answer is for efficiency reasons.

Structs are generally value types, and this can make processing data faster. As long as the overhead of building reference based objects from classes is low there is no need to introduce a second entity capable of storing data.

A data class is just a class that does nothing but store data. Kotlin's primary constructor syntax makes it very easy to create a data class as you simply have to list the properties as val or var in the primary constructor.

Thus the previous example can be written in Kotlin as:

class MyPersonClass(
    var firstName:String="",
    var secondName:String="",
    var age:Int=0
)

This is slightly simpler and the class so created is exactly the same but you can now initialize the properties using the primary constructor:

val person1=MyPersonClass(firstName="Mickey",     
                           secondName="Mouse",age=89)

Notice that you can create private properties by putting private in front of the parameter and read only properties by using val in place of var.

This data class idiom is so common Kotlin has extended it by recognizing that we use data classes in particular ways. 

You can include the modifier data in front of the class definition to create a Kotlin data class. In this case the properties are created as before but the compiler also generates some standard utility methods:

data class MyPersonClass(
    var firstName:String="",
    var secondName:String="",
    var age:Int=0
)

With a data class you also have automatically generated methods for:

equals – used for testing equality of content of two data classes

The equals method is used to implement the == relational operator. This tests for equality of type and content. That is, if two instances of the data object have the same data stored in their properties, == is true. Compare this to the === operator which tests for equality of reference i.e. do two variables reference the same object?

Equality is such an important topic that it deserves a section to itself.

hashcode – generates a hash code based on the content

The hashcode method computes a unique hash for every object, but for a data class the hashcode is only computed using the data properties, which means two instances of the class with the same data have the same hashcode. More on this in the section on Kotlin equality.

componentN – access functions used in destructuring.

Basically these are functions that allow other functions to access the data properties without knowing their names. They allow data classes to be used in destructuring – see later. 

For example:

val(firstName,secondName,age)=person1

automatically stores each of the properties in the variables on the left. Notice that assignment is based on property order and not the names involved. See the section on destructuring later in this chapter.

toString – a toString function that uses the content

The default toString method simply consists of the name of class followed by the hash value of the instance e.g:

MyPersonClass@5e2de80c>

The generated toString for a data class creates a string with each property and its current value. For example

If you don't put data in front of the class declaration then you still get inherited equals, hashcode and toString methods but these are inherited from the Any class and are very basic – in particular they don't make use of the data properties.

With a data class you get compiler generated custom methods tailored to the data in the class.

For example an instance of the MyPersonClass generates:

MyPersonClass(firstName=Mickey, secondName=Mouse, age=89)

copy – makes a copy of an instance

The copy method will create the new instance for you and set its properties to the same values as the old instance. For example to create a copy of an instance of MyPersonClass all you need is:

val person3=person1.copy()

The generated copy method has default values set to the current values of the instance being copied.

For example, in the case of the MyPersonClass, copy is defined as:

fun copy(firstName:String=this.firstName,
         secondName:String=this.secondName,
         var age:Int=this.age)

This means that if you want to change any of the properties during the copy you can by providing new values that override the defaults. For example:

val person3=person1.copy(firstName="Minnie")

changes just the firstName property.

This is a technique worth remembering.

Equality

A fundamental task in programming is to work out when two things are equal. This is especially important in the case of data classes, and it is the reason the system generates a custom equals method for you.

Kotlin has two equality operators == and === and their negation != and !==.

The simpler operator is === which is a reference equality. This just tests to see if two variables are referencing the same object. For example:

val person1=MyPersonClass(firstName="Mickey",
                          secondName="Mouse",age=89)
val person2=MyPersonClass(firstName="Mickey",
                          secondName="Mouse",age=89)
println( person1 === person2 )

Prints false as even though the data classes may appear to be equal given they have the same property values, they are different objects that just happen to have the same property values.

The == operator tests for structural equality.

That is, all of the objects have to have identical properties. If this is the case then they are considered equal. Notice that the == operator is used in many other comparison operations – in, contains and so on.

The == operator is implemented by the equals function for the type being compared. If you implement your own classes and you want to compare them then you need to override the equals method that you inherit from Any.

The Kotlin documentation states that any equals method must behave like an equality operator. It should be: 

  • reflexive a==a is true

  • symmetric if a==b is true then b==a

  • transitive if a==b and b==c then a==c

and

  • obviously it should be consistent in the sense it should return the same result if there are no changes to the objects involved.

There is another less obvious condition. If two objects are equal then their hashCode functions should return the same hash value. What this means is that if you override equals you have to override hashCode as well.

kotlinlogo



Last Updated ( Monday, 09 October 2017 )