About data classes, named parameters & default values

Maria Neumayer
A problem like Maria
4 min readJun 11, 2017

Writing data models in Java is a pain. You have to write getters, setters, toString(), hashCode() and equals() methods, constructors and possibly builders or a copy() method. For complex models this can easily create classes of a few hundred lines. Not only is it tedious to write all of those methods, it's also error prone. What if you add a new field? You might forget updating your hashCode() or equals() method.

While there’s tools like Lombok or AutoValue that help with that they’re still not ideal. They’re not part of the language and don’t work well with the IDE — even with plugins. In Kotlin you can write all of this in one line:

data class Song(val title: String, val album: String, val artist: String, val year: Int)

This includes all the getters, toString(), hashCode(), equals() and copy() methods, and the constructor. If you change a val to var you also get a setter. This is great, right? But what a about builders? Well you could still write it yourself, but in most cases you won't need them because of named parameters and default values.

Named Parameters

Builders are useful when you have complex classes that take in a lot of values. Especially when some of them are just boolean or int values — what does true mean here? What is 16? You have to look at the constructor to see what property you’re setting. It’s also error prone: you might put values in the wrong order.

Builders help with that: you can set all your parameters in any order you want and you will always know what exact value you’re setting.

In Kotlin named parameters help with that. First — let’s create a new song in Java:

new Song("Cristina", "Breakfast", "Teleman", 2014);

So what’s the artist, what’s the album and what’s the title again? Let’s look at the Kotlin code with named parameters to find out:

Song(title = "Cristina",
artist = "Teleman",
album = "Breakfast",
year = 2014)

Aha! Much clearer! You might’ve noticed that I swapped the artist and album around. That’s because you can change the order of the parameters — as long as you’re using the named parameters the right parameter gets set.

This works great for Kotlin, but if you’re still using Java in your project you can still create a temporary builder to use from Java. It’ll be a great reward to remove it once you’ve migrated all the necessary code to Kotlin.

Default Values

Named parameters are brilliant, but sometimes you don’t want to write the same set of parameters again and again. Let’s look at the song model above and extend it a bit.

data class Song(val title: String,
val album: String,
val artist: String,
val year: Int,
val playCount: Int,
val rating: Int,
val language: String)

That would be quite a lot of code to write to create a song! Here come default values:

data class Song(val title: String,
val album: String,
val artist: String,
val year: Int,
val playCount: Int = 0,
val rating: Int = 0,
val language: String = "English")

You might’ve noticed val playCount: Int = 0. This means every time you instantiate a song and don't provide the count it'll automatically be set to 0. Let's have a look at the code:

Song(title = "Cristina",
artist = "Teleman",
album = "Breakfast",
year = 2014)

Even though we’ve added a few more parameters you can create a song without any new code. Any parameter that you provide will be set, if not the default value will be used. However when creating it from Java you still have to provide all values. To workaround that you can add the @JvmOverloads annotation to expose overloads for all the required constructors.

Default values not only work in constructors, but also in functions. One area where I found that useful were fixtures in tests. Say you have the song model above. You might want to write some tests for some app logic depending on if the song was rated or not or was released in 2017. Obviously you could just create a new song for every test and set all values, but that can be tedious, especially for more complex models.

In Java code I used to create fixtures which returned a builder, which I could then modify whenever needed. Now in Kotlin I don’t write builders anymore, but I found a nice way to work around that using default values:

fun createSong(title: String = "Cristina",
album: String = "Breakfast",
artist: String = "Teleman",
year: String = 2014,
playCount: Int = 0,
rating: Int = 0,
language: String = "English")
= Song(title, album, artist, year, playCount, rating, language)

All arguments of this function have default values set. This means that every time I create a fixture I only have to set the values I care about and want to test using named parameters. Here’s an example:

val ratedSong = createSong(rating = 5)

Neat, right? I only care about the rated value in this case — everything else can be the default value as I’m not testing for it.

What else can you do with all of that?

Data classes, named parameters and default values are very powerful and there’s many use cases for them.

Data classes work great for for all your models, including your Api models — they work well with Json parsing libraries like Gson or Moshi.

Named parameters are useful for any complex functions or constructors, but even if they’re not — if it is not clear what you’re assigning use named parameters — it’ll make your code much easier to read, especially when reviewing your code.

Default values have so many great use cases — whenever you want to overload a function you can use default values instead. This is especially powerful when used with extension functions, but onto that another time…

--

--

Maria Neumayer
A problem like Maria

European living in London. Principal Engineer at @SkyscannerEng. Tweeting at @marianeum.