Kotlin Data Classes, shallow copies and immutability
Tags: kotlin, jvm, development, android
Published: 2022-08-08 12:35:23

---
title: Kotlin Data Classes, shallow copies and immutability
published: true
date: 2022-07-28 10:03:26 UTC
tags: kotlin,jvm,development,android
canonical_url: https://lengrand.fr/kotlin-data-classes-shallow-copies-and-immutability/
---

 ![Kotlin Data Classes, shallow copies and immutability](https://lengrand.fr/content/images/2022/07/Why-Copy-Paste-Does-Not-Work-and-What-to-do-about-it.webp)

_TL;DR: The data class `copy` method in Kotlin creates shallow copies and data classes are NOT immutable data structures by themselves. They become immutable though, if all of their properties are immutable themselves (\_val\_)._

_Note: You can run all the samples listed here [by clicking that link](https://pl.kotl.in/CzsYyKC_l)_

Earlier this week, I gave a Kotlin introduction training to about 50 folks at Adyen. I think most of them appreciated the training and I even got some nice feedback

> I gave a "introduction to Kotlin" workshop yesterday for about 50 people for the first time. Damn workshops are much more exhausting than I was expecting.   
>   
> This feedback made my day though 🥰❤️. [pic.twitter.com/jZE8r1UBWJ](https://t.co/jZE8r1UBWJ)
> 
> — Julien Lengrand-Lambert (@jlengrand) [July 26, 2022](https://twitter.com/jlengrand/status/1551904525358874625?ref_src=twsrc%5Etfw)

<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
_https://twitter.com/jlengrand/status/1551904525358874625_

One fun thing about the training is that most of the audience was coming from a C/C++ background and they asked many questions about references / values, shallow or deep copies and how Kotlin manages memory. Suffice to say I wasn't ready for it ^^. This blog summarises my findings after the training.

## A quick recap about Data Classes

In Kotlin, [data classes](https://kotlinlang.org/docs/data-classes.html) are specialised structures that are meant hold data (as the name suggests).

```
data class Address(val number: Int, val street: String, val city: String)

data class User(val name: String, val age: Int, val address: Address)

```

They come with additional goodies compared to normal classes, among which:

- generated `hashCode` and `equals` method. (Equals is smart as well, checking if all properties and sub properties have the same value) 
- A smart `toString` method that displays the data content nicely

Duh, that's just like Java Records, I hear you say. Yeah, except that data classes also come with the `copy` method, which is in my opinion give them all their power. With that copy method, you can create a new copy of an existing instance, while modifying some data at the same time. Here is an example:

```
val bob = User("Bob", 42, Address("12", "rue des peupliers", "Paris"))
val anOlderBob = bob.copy(age = 43)
```

As I was presenting this to the C++ folks, I heard the same question pop up at three different places at the same time : Is `anOlderBob` a shallow, or deep copy of `bob`?

In other words, if `bob` changes address now, does `anOlderBob` get affected?

Silence in the room.....

## Benefits of Immutable data classes

See, I've been interested in Functional Programming for a little while, and I know that most of them heavily rely on immutable data structures. One of the main reasons for this is thread safety. If your data cannot be modified any more once it has been written, you are by definition thread safe and you are certain not to have synchronisation issues. I even think that the first person who taught me this was [the one and only Martin Odersky](https://www.youtube.com/watch?v=3jg1AheF4n0).

Thing is, the huge majority of applications out there are in the business of moving data around. And if you cannot modify existing objects, well it also means that you're gonna have to create many many more objects to compensate for it right?

Let's take an example :

```
data class User(val name: String, val age: Int)

val users = listOf(
        User("Bob", 42), 
        User("Georges", 12), 
        User("Emily", 25), 
        User("Amy", 46))
val olderUsers = users.map { it.copy(age = it.age + 1) }

>> [User(name=Bob, age=42), User(name=Georges, age=12), User(name=Emily, age=25), User(name=Amy, age=46)]
>> [User(name=Bob, age=43), User(name=Georges, age=13), User(name=Emily, age=26), User(name=Amy, age=47)]
```

Here, we are creating a list of 4 users and then creating a new list with each user older by one year. Because our users are all immutable, we have to create a new copy for each user.

For more serious applications, the obvious outcome is **that this type of copying** _ **has** _ **to be shallow** (meaning our new object's properties link to the memory location of the parent properties), because otherwise the performance hit of creating so many objects would be prohibitive.

Well, now let's verify it.

## Playing around with Data Classes

Let's run a few tests to make sure our assumptions are correct (or not). We create `FantasyHero`, a data class that has mutable and immutable properties, of primitive and more complex types.

```
enum class WEAPONS{
    AXE, SWORD, WAND, BOW
}

enum class CLASS{
    WIZARD, WARRIOR, PALADIN, THIEF
}

data class Origin(val city: String, var country: String)

data class FantasyHero(
	var name: String, 
    val weapons: MutableList<WEAPONS>, 
    var heroClass: CLASS?, 
    val origin: Origin = Origin("Utrecht", "The Netherlands")
)
```

First, let's check that everything behaves as expected

```
val gandalf = FantasyHero(
	"Gandalf the Grey", 
    mutableListOf(WEAPONS.WAND), 
    CLASS.WIZARD
)
val anotherGandalf = FantasyHero(
	"Gandalf the Grey", 
    mutableListOf(WEAPONS.WAND), 
    CLASS.WIZARD
)
val gandalfCopy = gandalf.copy()

val betterGandalf = gandalf.copy(name="Gandalf the White")

println(gandalf)
println(anotherGandalf)
println(betterGandalf)

println(gandalf == anotherGandalf)
println(gandalf == gandalfCopy)
println(gandalf == betterGandalf)
println("--")
println(gandalf === anotherGandalf)
println(gandalf === gandalfCopy)
println(gandalf === betterGandalf)

>> FantasyHero(name=gandalf the grey, weapons=[WAND], heroClass=WIZARD, origin=Origin(city=Utrecht, country=The Netherlands))
>> FantasyHero(name=gandalf the grey, weapons=[WAND], heroClass=WIZARD, origin=Origin(city=Utrecht, country=The Netherlands))
>> FantasyHero(name=gandalf the white, weapons=[WAND], heroClass=WIZARD, origin=Origin(city=Utrecht, country=The Netherlands))

>> true
>> true
>> false
>> --
>> false
>> false
>> false
```

That all checks out. We created 2 different instances with the same data and they are considered equal (because we check the property values against each other, not the object instances). The straight copy of our instance is equal as well, while the instance where we modified a property isn't any more. And we also check that all instances of heroes are different. They are _not_ the same object. All good, we can continue

### Mutating "complex" properties

First, let's give our Wizard an extra weapon. We all know he yields a sword as well after all. Since we expect copies to be shallow copies here, we would expect the list of weapons to be changed in all copies of the hero if we change the original list. And that's exactly what happens :

```
gandalf.weapons.add(WEAPONS.SWORD)
println(gandalf.weapons)
println(anotherGandalf.weapons)
println(gandalfCopy.weapons)
println(betterGandalf.weapons)

>> [WAND, SWORD]
>> [WAND]
>> [WAND, SWORD]
>> [WAND, SWORD]

// Same behaviour in case we change one of the later copies btw
anotherGandalf.weapons.remove(WEAPONS.SWORD)
println(gandalf.weapons)
println(anotherGandalf.weapons)
println(gandalfCopy.weapons)
println(betterGandalf.weapons)

>> [WAND]
>> [WAND]
>> [WAND]
>> [WAND]

```

In the same manner, modifying a property that is a data class itself yields the same types of result, all copies are affected :

```
gandalf.origin.country = "France"
println(gandalf.origin.country)
println(anotherGandalf.origin.country)
println(betterGandalf.origin.country)

>> France
>> The Netherlands
>> France
>> France
```

### Mutating a simple property

So far so good. Let's change Gandalf's name!

```
gandalf.name = "Gandalf the Blue"
println(gandalf.name)
println(anotherGandalf.name)
println(gandalfCopy.name)
println(betterGandalf.name)
    
>> Gandalf the Blue
>> Gandalf the Grey
>> Gandalf the Grey
>> Gandalf the White
```

When mutating the `String` property of our Wizard, we see that the name change isn't propagated to any of the copies.

Doing the same test with an `Enum` leads to a similar result

```
gandalf.heroClass = CLASS.PALADIN
println(gandalf.heroClass)
println(anotherGandalf.heroClass)
println(gandalfCopy.heroClass)
println(betterGandalf.heroClass)

>> PALADIN
>> WIZARD
>> WIZARD
>> WIZARD
```

Now, that had me confused at first. [Confused enough to ask on the Kotlin Slack actually](https://kotlinlang.slack.com/archives/C0B8RC352/p1658928923155649) (thanks for the help folks!) . Are the copies holding references to the same values or not? Well, it turns out that they are and the explanation goes back to the basics of Kotlin / Java :  References are immutable, so modifying them literally leads to referencing a new Object instead. The behaviour has nothing to do with data classes :

```
var heroClass = CLASS.WARRIOR
val theClass = heroClass
heroClass = CLASS.WIZARD

println(heroClass)
println(theClass)

>> WIZARD
>> WARRIOR
```

When copying data classes around, the constructor passes references to all of the properties, and all of those references are immutable.

## Conclusion

Just like we expected, the data class `copy` method create shallow copies of instances. We would have saved time [just checking the documentation](https://kotlinlang.org/docs/data-classes.html#copying) actually:

![Kotlin Data Classes, shallow copies and immutability](https://lengrand.fr/content/images/2022/07/image.png)
_A screenshot of the documentation about copying data classes (https://kotlinlang.org/docs/data-classes.html#copying)_

Now back to our question : Are data classes thread safe? Well now I realize that there's been a confusing in my head during the training. **Data classes have little to do with immutability, they are a convenient way to work with objects purely holding data**. However, you _can_ make them immutable by making sure all of their fields are immutable themselves. That's what I typically do, and you probably should too 😊.

## Further reading

Interestingly, [Romain pointed at a KEEP](https://twitter.com/romainbsl/status/1552550074051330048) in the Twitter thread which quite literally discusses the topic mentioned here. I'm keen on seeing what's gonna happen with Value Classes in the future.

_Hit me up [@jlengrand](https://twitter.com/jlengrand), if you have questions or remarks, I'm always up for learning new things 😊_