Open In App

Kotlin Inline classes

Last Updated : 25 Aug, 2021
Improve
Improve
Like Article
Like
Save
Share
Report

Inline classes are introduced by Kotlin since Kotlin 1.3 version to overcome the shortcomings of traditional wrappers around some types.These Inline classes add the goodness of Typealiases with the value range of the primitive data types.

Let us suppose that we are selling some items and the cost is defined as a float type.This is depicted in the following data class 

data class Items(val itemno: Int, val cost: float, val qty: Int)

If we support two types of currencies like dollar and rupees, we need to refactor cost in another class.

Java




data class Items(val itemno: Int, val cost: Cost, val qty: Int)
data class Cost(val value: Float, val currency: Currency)
enum class Currency {
    RUPEE,
    DOLLAR
}


The above method has two problems: 
1.Memory overhead 
2.Complexity 
These two problems are overcome by Inline classes 

Java




data class Item(val id: Int, val price: RupeePrice, val qty: Int)
inline class RupeePrice(val price: Float) {
    inline fun toDollars(): Float = price * 71.62f
}


An inline class must have a single property initialized in the primary constructor. At runtime, instances of the inline class will be represented using this single property:data of the class is “inlined” into its usages (That’s why the name “Inline classes”).

Members

They are similar to regular classes in the sense that they are allowed to declare properties and functions.However, they have certain limitations too.Inline classes cannot have init blocks nor can they have complex computable properties like lateinit/delegated properties. 

Java




inline class Name(val s: String) {
    val length: Int
        get() = s.length
    fun greet() {
        println("Hello, $s")
    }
}   
fun main() {
    val name = Name("Kotlin")
    name.greet() // method `greet` is called as a static method
    println(name.length) // property getter is called as a static method
}


Inheritance

These classes are allowed to inherit from Interfaces but can not extend other classes and must be final

Java




interface Printable {
    fun prettyPrint(): String
}
inline class Name(val s: String) : Printable {
    override fun prettyPrint(): String = "Let's $s!"
}   
fun main() {
    val name = Name("Kotlin")
    println(name.prettyPrint()) // Still called as a static method
}


Representation

Inline classes can be represented as either wrappers or underlying type.Though the latter is preferred, sometimes it is useful to keep wrappers around.Necessarily they are boxed whenever used as other type. Referential equality is meaningless as it can be represented both as an underlying value and as a wrapper.

Java




interface I
inline class Foo(val i: Int) : I
fun asInline(f: Foo) {}
fun  asGeneric(x: T) {}
fun asInterface(i: I) {}
fun asNullable(i: Foo?) {}
fun  id(x: T): T = x
fun main() {
    val f = Foo(42)
   
    asInline(f)    // unboxed: used as Foo itself
    asGeneric(f)   // boxed: used as generic type T
    asInterface(f) // boxed: used as type I
    asNullable(f)  // boxed: used as Foo?, which is different from Foo
     
    // below, 'f' first is boxed (while being passed to 'id') and then unboxed (when returned from 'id')
    // In the end, 'c' contains unboxed representation (just '42'), as 'f'
    val c = id(f) 
}


As an underlying type, these inline classes may lead to obscure errors like platform signature crashes.

Java




inline class UInt(val x: Int)
// Represented as 'public final void compute(int x)' on the JVM
fun compute(x: Int) { }
// Also represented as 'public final void compute(int x)' on the JVM!
fun compute(x: UInt) { }


To prevent such errors we use a process called Mangling where we add some hashcode to function name.Therefore, fun compute(x: UInt) will be represented as public final void compute-(int x), which solves the problem.

Inline classes vs type aliases

Though both may appear similar, the type aliases are assignment-compatible with underlying type. Also inline classes introduce a completely new type whereas type aliases give an alternate name for existing type

Java




typealias NameTypeAlias = String
inline class NameInlineClass(val s: String)
fun acceptString(s: String) {}
fun acceptNameTypeAlias(n: NameTypeAlias) {}
fun acceptNameInlineClass(p: NameInlineClass) {}
fun main() {
    val nameAlias: NameTypeAlias = ""
    val nameInlineClass: NameInlineClass = NameInlineClass("")
    val string: String = ""
 
    acceptString(nameAlias) // OK: pass alias instead of underlying type
    acceptString(nameInlineClass) // Not OK: can't pass inline class instead of underlying type
 
    // And vice versa:
    acceptNameTypeAlias(string) // OK: pass underlying type instead of alias
    acceptNameInlineClass(string) // Not OK: can't pass underlying type instead of inline class
}


The design of inline classes is new and no compatibility guarantees are given.In Kotlin 1.3+, a warning will be reported, indicating that this feature is experimental.To remove this we have to opt in to the usage of this experimental feature by passing the compiler argument -Xinline-classes.



Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads