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…
Job Offers
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:
https://kotlinlang.org/docs/inline-classes.html?source=post_page—–090fe64829e6——————————–#members
This is previously published on proandroiddev.com