1.1.50.17. fejezet, Beágyazott értékosztályok

value class Password(private val s: String)

Néha szükségünk van arra, hogy becsomagoljunk alaptípusokat osztályokba. Performencia szempontjából ez azonban nem szerencsés megoldás, mivel a JVM optimalizálja az alaptípusokkal kapcsolatos műveleteket. Ezen segít az inline oszály. JVM környezethez használjuk a @JvmInline annotációt.

@JvmInline
value class Password(private val s: String)
 
// No actual instantiation of class 'Password' happens
// At runtime 'securePassword' contains just 'String'
val securePassword = Password("Don't try this in production")

Tagok

@JvmInline
value class Person(private val fullName: String) {
    init {
        require(fullName.isNotEmpty()) {
            "Full name shouldn't be empty"
        }
    }
 
    constructor(firstName: String, lastName: String) : this("$firstName $lastName") {
        require(lastName.isNotBlank()) {
            "Last name shouldn't be empty"
        }
    }
 
    val length: Int
        get() = fullName.length
 
    fun greet() {
        println("Hello, $fullName")
    }
}
 
fun main() {
    val name1 = Person("Kotlin", "Mascot")
    val name2 = Person("Kodee")
    name1.greet() // the `greet()` function is called as a static method
    println(name2.length) // property getter is called as a static method
}

Öröklődés

interface Printable {
    fun prettyPrint(): String
}
 
@JvmInline
value 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
}

Az inline osztályok számára tilos az osztályhierarchiában való részvétel. Ez azt jelenti, hogy az inline osztályok nem bővíthetnek más osztályokat, és mindig final

Reprezentáció

A generált kódban a Kotlin fordító minden inline osztályhoz egy wrapper-t tart. A beágyazott osztálypéldányok futásidőben wrapper-ként vagy mögöttes típusként is ábrázolhatók. Ez hasonló ahhoz, ahogy az Int ábrázolható primitívként és Integer wrapper-ként.

interface I
 
@JvmInline
value class Foo(val i: Int) : I
 
fun asInline(f: Foo) {}
fun <T> asGeneric(x: T) {}
fun asInterface(i: I) {}
fun asNullable(i: Foo?) {}
 
fun <T> 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)
}
@JvmInline
value class UserId<T>(val value: T)
 
fun compute(s: UserId<String>) {} // compiler generates fun compute-<hashcode>(s: Any?)

Mangling

Mivel az inline osztályok az alapul szolgáló típusuknak megfelelően vannak lefordítva, ez különböző homályos hibákhoz vezethet, például váratlan platformaláírás-ütközésekhez.

Az ilyen problémák enyhítése érdekében a beágyazott osztályokat használó függvények úgy vannak összezavarva, hogy stabil hashkódot adnak hozzá a függvény nevéhez. (pl.: public final void compute-(int x))

@JvmInline
value 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) { }

Meghívás Java kódból

@JvmInline
value class UInt(val x: Int)
 
fun compute(x: Int) { }
 
@JvmName("computeUInt")
fun compute(x: UInt) { }

Beágyazott osztályok és típus aliaszok

typealias NameTypeAlias = String
 
@JvmInline
value 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
}

Beágyazott osztályok és delegáció

interface MyInterface {
    fun bar()
    fun foo() = "foo"
}
 
@JvmInline
value class MyInterfaceWrapper(val myInterface: MyInterface) : MyInterface by myInterface
 
fun main() {
    val my = MyInterfaceWrapper(object : MyInterface {
        override fun bar() {
            // body
        }
    })
    println(my.foo()) // prints "foo"
}