1.1.50.18. fejezet, Objektum kifejezések és deklarációk

Néha létre kell hoznia egy olyan objektumot, amely egy osztály enyhe módosítása, anélkül, hogy kifejezetten új alosztályt deklarálna. A Kotlin ezt objektumkifejezésekkel és objektumdeklarációkkal tudja kezelni.

Anonim objektumok az alapoktól

val helloWorld = object {
    val hello = "Hello"
    val world = "World"
    // object expressions extend Any, so `override` is required on `toString()`
    override fun toString() = "$hello $world"
}
 
print(helloWorld)

Névtelen objektumok öröklése szupertípusokból

window.addMouseListener(object : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) { /*...*/ }
 
    override fun mouseEntered(e: MouseEvent) { /*...*/ }
})

Ha egy szupertípusnak van konstruktora, adjuk át neki a megfelelő konstruktor paramétereket. Több szupertípus is megadható vesszővel tagolt listaként a kettőspont után.

open class A(x: Int) {
    public open val y: Int = x
}
 
interface B { /*...*/ }
 
val ab: A = object : A(1), B {
    override val y = 15
}

Névtelen objektumok használata visszatérési és értéktípusként

class C {
    private fun getObject() = object {
        val x: String = "x"
    }
 
    fun printX() {
        println(getObject().x)
    }
}
interface A {
    fun funFromA() {}
}
interface B
 
class C {
    // The return type is Any; x is not accessible
    fun getObject() = object {
        val x: String = "x"
    }
 
    // The return type is A; x is not accessible
    fun getObjectA() = object: A {
        override fun funFromA() {}
        val x: String = "x"
    }
 
    // The return type is B; funFromA() and x are not accessible
    fun getObjectB(): B = object: A, B { // explicit return type is required
        override fun funFromA() {}
        val x: String = "x"
    }
}

Változók elérése névtelen objektumokból

fun countClicks(window: JComponent) {
    var clickCount = 0
    var enterCount = 0
 
    window.addMouseListener(object : MouseAdapter() {
        override fun mouseClicked(e: MouseEvent) {
            clickCount++
        }
 
        override fun mouseEntered(e: MouseEvent) {
            enterCount++
        }
    })
    // ...
}

Objektum deklarációk

A Singleton minta számos esetben hasznos lehet, és a Kotlin megkönnyíti a szingulárisok deklarálását.

object DataProviderManager {
    fun registerDataProvider(provider: DataProvider) {
        // ...
    }
 
    val allDataProviders: Collection<DataProvider>
        get() = // ...
}

Ezt objektumdeklarációnak nevezzük, és mindig van egy neve az object kulcsszó után. A változódeklarációhoz hasonlóan az objektumdeklaráció sem kifejezés, és nem használható a hozzárendelési utasítás jobb oldalán. Az objektumdeklaráció inicializálása szálbiztos, és az első hozzáféréskor történik.

Az objektumra hivatkozás így néz ki:

DataProviderManager.registerDataProvider(...)

Ezeknek az objektumoknak is lehet szupertípusa:

object DefaultListener : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) { ... }
 
    override fun mouseEntered(e: MouseEvent) { ... }
}

Adat objektumok

data object MyDataObject {
    val x: Int = 3
}
 
fun main() {
    println(MyDataObject) // MyDataObject
}

Ezeknél az objektumoknál sose használjuk a referencia szerinti összehasonlítás operátort (===). Ez segít elkerülni a buktatókat, ha egy adatobjektum egynél több példánya létezik futásidőben.

import java.lang.reflect.Constructor
 
data object MySingleton
 
fun main() {
    val evilTwin = createInstanceViaReflection()
 
    println(MySingleton) // MySingleton
    println(evilTwin) // MySingleton
 
    // Even when a library forcefully creates a second instance of MySingleton, its `equals` method returns true:
    println(MySingleton == evilTwin) // true
 
    // Do not compare data objects via ===.
    println(MySingleton === evilTwin) // false
}
 
fun createInstanceViaReflection(): MySingleton {
    // Kotlin reflection does not permit the instantiation of data objects.
    // This creates a new MySingleton instance "by force" (i.e. Java platform reflection)
    // Don't do this yourself!
    return (MySingleton.javaClass.declaredConstructors[0].apply { isAccessible = true } as Constructor<MySingleton>).newInstance()
}

Adatobjektumok használata lezárt hierarchiával

sealed interface ReadResult
data class Number(val number: Int) : ReadResult
data class Text(val text: String) : ReadResult
data object EndOfFile : ReadResult
 
fun main() {
    println(Number(7)) // Number(number=7)
    println(EndOfFile) // EndOfFile
}

Kísérő objektumok

Java static tulajdonság megfelelője, példányosított osztály változója.

class MyClass {
    companion object Factory {
        fun create(): MyClass = MyClass()
    }
}
 
val instance = MyClass.create()

A kísérő objektum neve elhagyható, ebben az esetben a Compation név kerül felhasználásra:

class MyClass {
    companion object { }
}
 
val x = MyClass.Companion

Az osztály tagjai hozzáférhetnek a megfelelő társobjektum privát tagjaihoz.

Egy osztály neve önmagában (nem egy másik név minősítőjeként) az osztály társobjektumára való hivatkozásként szolgál (függetlenül attól, hogy megnevezett-e vagy sem):

class MyClass1 {
    companion object Named { }
}
 
val x = MyClass1
 
class MyClass2 {
    companion object { }
}
 
val y = MyClass2

Vegye figyelembe, hogy bár a társobjektumok tagjai statikus tagoknak tűnnek más nyelvekben, futásidőben ezek továbbra is valós objektumok példánytagjai, és például interfészeket valósíthatnak meg:

interface Factory<T> {
    fun create(): T
}
 
class MyClass {
    companion object : Factory<MyClass> {
        override fun create(): MyClass = MyClass()
    }
}
 
val f: Factory<MyClass> = MyClass

Van három fontos szemantikai különbség az objektumkifejezések és az objektumdeklarációk között:

  • Az objektumkifejezések végrehajtása (és inicializálása) azonnal megtörténik, ahol használatban vannak.
  • Az objektumdeklarációk inicializálása lazy módon történik, amikor először férnek hozzá.
  • A társobjektum inicializálása akkor történik, amikor a megfelelő osztály betöltődik (fel van oldva), amely megfelel a Java statikus inicializáló szemantikájának.