Kotlin inline value classes explained

Michal Ankiersztajn
ProAndroidDev
Published in
3 min readFeb 28, 2024

Often, when working with a single value used commonly across your project, you’ll want to wrap it in a class to encapsulate its logic and avoid making multiple Util classes for it.

Here’s an example of such a class for Name that is a String , but we’ll often want to format it in different ways.

class Name(
private val name: String,
) {
fun hello() = "Hello $name"

...
}

1. The Problem

However, we sacrifice efficiency for readability because the program needs to allocate a new class.

It can become severe and slow your app when:

  • We need to create many objects. We’re introducing a lot of overhead in runtime due to allocations.
  • We’re working with primitive types. Primitive types are heavily optimised in runtime, and we lose all of those optimisations by wrapping them.

2. The Solution

By using Kotlin, value class we avoid allocations. In runtime, no additional classes are going to be created. Here’s how you need to modify the class:

// Required when working with Kotlin in JVM
@JvmInline
value class Name(
private val name: String,
) {
fun hello() = "Hello $name"

...
}

No instantiation of a Name class happens. Instead, the class is represented by a single property, in this case a String .

fun main() {
// No instantiation of a class Name happens
// It's represented by a String
val userName = Name("Michael")
}

Now that we know all of this, what are the limitations of such classes? Frankly speaking, the biggest limitation is that they only work when we have single property classes. Apart from that, they can be used like normal classes:

3. Functions, init and constructors

Here’s an example of what we can do with the Name class:

@JvmInline
value class Name(
private val name: String,
) {
init {
if (name.isBlank()) throw IllegalArgumentException()
}

constructor(firstName: String, secondName: String) : this("$firstName $secondName")

fun hello() = "Hello $name"
}

As you can see, you can do everything a normal class would do and get the performance benefits if it has a single field. We’re even able to use…

4. Inheritance

It’s not uncommon for these types of classes to have to implement some type of interface which inline class supports. For example, we could make our Name class implement Greetable interface.

interface Greetable {
fun greet(): String
}

value class Name(
private val name: String,
): Greetable {
override fun greet(): String = "Greetings $name"
}

5. Boxing and unboxing

To be able to use inline class like a normal class Kotlin compiler will keep a wrapper for each of the inline class . It works similarly to the Int , which in Kotlin can be treated as a primitive and a wrapper.

Using a wrapper is called boxing, while using underlying types is called unboxing. In short, Kotlin will box the inline class whenever it's used as another type. Here are a couple of examples:

fun useInlined(name: Name) {}
fun useNullable(name: Name?) {}
fun useInterface(greetable: Greetable) {}
fun <T> useGeneric(t: T) {}

fun main() {
val name = Name("Michael")

useInlined(name) // Unboxed: compiler will use a String
useNullable(name) // Boxed
useInterface(name) // Boxed
useGeneric(name) // Boxed
}

If you’ve found this article helpful, please clap so it’s easier for others to see it, and follow me for more!

Based on:

--

--