Generikusok Kotlinban

1. Áttekintés

Ebben a cikkben megnézzük a általános típusok a Kotlin nyelvben.

Nagyon hasonlítanak a Java nyelvéhez, de a Kotlin nyelvű alkotók speciális kulcsszavak, például ki és ban ben.

2. Paraméterezett osztályok létrehozása

Tegyük fel, hogy egy paraméterezett osztályt akarunk létrehozni. Könnyen megtehetjük ezt Kotlin nyelven általános típusok használatával:

class ParameterizedClass (privát érték: A) {fun getValue (): A {return value}

Létrehozhatunk egy ilyen osztály példányát úgy, hogy a konstruktor használatakor kifejezetten beállítunk egy paraméterezett típust:

val parameterizedClass = ParameterizedClass ("string-value") val res = paraméterezettClass.getValue () assertTrue (res karakterlánc)

Szerencsére Kotlin a paramétertípusból kikövetkeztetheti az általános típust, így ezt elhagyhatjuk a konstruktor használatakor:

val parameterizedClass = ParameterizedClass ("string-value") val res = paraméterezettClass.getValue () assertTrue (res String)

3. Kotlin ki és ban ben Kulcsszavak

3.1. A Ki Kulcsszó

Tegyük fel, hogy olyan termelői osztályt akarunk létrehozni, amely valamilyen T típusú eredményt produkál. Néha; azt az előállított értéket szeretnénk hozzárendelni egy referenciához, amely egy T típusú szupertipus.

Ennek elérése a Kotlin segítségével, használnunk kell aki kulcsszó az általános típuson. Ez azt jelenti, hogy ezt a hivatkozást bármelyik szupertípusához hozzárendelhetjük. Az out értéket csak az adott osztály állíthatja elő, de nem fogyaszthatja el:

class ParameterizedProducer (privát val érték: T) {fun get (): T {return value}}

Meghatároztuk a ParameterizedProducer osztály, amely képes előállítani egy T típusú értéket.

Következő; hozzárendelhetjük a ParameterizedProducer osztály a hivatkozáshoz, amely annak szupertippje:

val parameterizedProducer = ParameterizedProducer ("string") val ref: ParameterizedProducer = parameterizedProducer assertTrue (a ref ParameterizedProducer)

Ha a típus T ban,-ben ParamaterizedProducer osztály nem lesz az ki típusú, az adott utasítás fordítói hibát eredményez.

3.2. A ban ben Kulcsszó

Néha ellentétes helyzet áll rendelkezésünkre, ami azt jelenti, hogy van egy típusú referenciánk T és szeretnénk tudni hozzárendelni a. altípushoz T.

Használhatjuk a ban ben kulcsszó az általános típusra, ha azt az altípus hivatkozásához akarjuk rendelni. A kulcsszóban csak a fogyasztott, nem előállított paramétertípuson használható:

class ParameterizedConsumer {fun toString (érték: T): String {return value.toString ()}}

Kijelentjük, hogy a toString () A metódus csak egy típusú értéket fogyaszt T.

Ezután hozzárendelhetünk egy típusú referenciát Szám altípusának hivatkozására - Kettős:

val parameterizedConsumer = ParameterizedConsumer () val ref: ParameterizedConsumer = parameterizedConsumer assertTrue (a ref ParameterizedConsumer)

Ha a típus T ban,-ben ParameterizedCounsumer nem az lesz ban ben típusú, az adott utasítás fordítói hibát eredményez.

4. Írja be a vetületeket

4.1. Másolja az altípusok tömbjét a felső típusok tömbjébe

Tegyük fel, hogy van valamilyen típusú tömbünk, és az egész tömböt be akarjuk másolni a Bármi típus. Ez egy érvényes művelet, de ahhoz, hogy a fordító össze tudja állítani a kódunkat, meg kell jegyeznünk a bemeneti paramétert a ki kulcsszó.

Ez tudatja a fordítóval, hogy a bemeneti argumentum bármilyen típusú lehet, amely a Bármi:

szórakoztató másolat (from: Array, to: Array) {assert (from.size == to.size) for (i in from.indices) to [i] = from [i]}

Ha a tól től paraméter nem a ki Any típusú, nem fogunk tudni továbbadni egy tömböt Int írja be argumentumként:

val ints: Array = arrayOf (1, 2, 3) val any: Array = arrayOfNulls (3) copy (ints, any) assertEquals (any [0], 1) assertEquals (any [1], 2) assertEquals (any [ 2], 3)

4.2. Egy altípus elemeinek hozzáadása a felső típus tömbjéhez

Tegyük fel, hogy a következő helyzet áll rendelkezésünkre: van egy tömbünk Bármi típus, amelynek szupertípusa Int és hozzá akarunk adni egy Int eleme ennek a tömbnek. Használnunk kell a ban ben kulcsszó a céltömb egyik típusaként, hogy a fordító tudassa, hogy másolni tudjuk a Int értéke ennek a tömbnek:

szórakoztató kitöltés (dest: tömb, érték: int) {dest [0] = érték}

Ezután átmásolhatjuk a Int írja be a tömb Bármi:

val objektumok: Array = arrayOfNulls (1) kitöltés (objektumok, 1) assertEquals (objektumok [0], 1)

4.3. Csillagvetítések

Vannak olyan helyzetek, amikor nem érdekel az adott értéktípus. Tegyük fel, hogy csak egy tömb összes elemét szeretnénk kinyomtatni, és nem mindegy, hogy milyen típusúak a tömbben.

Ennek eléréséhez csillagvetítést használhatunk:

szórakoztató printArray (tömb: tömb) {tömb.forEach {println (it)}}

Ezután bármilyen típusú tömböt átadhatunk a printArray () módszer:

val array = arrayOf (1,2,3) printArray (tömb)

A csillagvetület referencia típusának használata esetén kiolvashatunk belőle értékeket, de nem írhatjuk őket, mert fordítási hibát okoz.

5. Általános korlátozások

Tegyük fel, hogy egy tömb elemet akarunk rendezni, és minden elemtípusnak meg kell valósítania a Hasonló felület. Az általános korlátozásokkal meghatározhatjuk ezt a követelményt:

szórakozás  rendezés (lista: lista): lista {visszatérési lista.rendezve ()}

Az adott példában meghatároztuk, hogy minden elem T szükséges a Hasonló felület. Ellenkező esetben, ha megpróbálunk átadni egy listát olyan elemeknek, amelyek nem valósítják meg ezt az interfészt, az fordítói hibát okoz.

Meghatároztuk a fajta függvény, amely argumentumként veszi fel a megvalósító elemek listáját Hasonló, így felhívhatjuk a rendezve () módszer rajta. Nézzük meg a módszer tesztesetét:

val listOfInts = listOf (5,2,3,4,1) val rendezett = sort (listOfInts) assertEquals (rendezve, listOf (1,2,3,4,5))

Könnyedén átadhatunk egy listát Ints mert a Int típus hajtja végre a Hasonló felület.

5.1. Több felső határ

A szögletes zárójel jelölésével legfeljebb egy általános felső határt deklarálhatunk. Ha egy típusú paraméterhez több általános felső határra van szükség, akkor külön kell használnunk hol záradékok az adott típusú paraméterhez. Például:

fun sort (xs: List) ahol T: CharSequence, T: összehasonlítható {// a gyűjtemény rendezése a helyén}

Amint fent látható, a paraméter T végre kell hajtania a CharSequence és Hasonló interfészek egyidejűleg. Hasonlóképpen deklarálhatunk több általános felső határral rendelkező osztályokat is:

class StringCollection (xs: List) ahol T: CharSequence, T: összehasonlítható {// kihagyva}

6. Generikusok futás közben

6.1. Típus Törlés

Ahogy a Java esetében, Kotlin generikusai is törölték futás közben. Vagyis egy általános osztály egy példánya futás közben nem őrzi meg a típusparamétereit.

Például, ha létrehozunk egy Készlet és tegyen bele néhány húrt, futás közben csak a-ként láthatjuk Készlet.

Hozzunk létre kettőt Készletek két különböző típusú paraméterrel:

val könyvek: Set = setOf ("1984", "Brave new world") val primes: Set = setOf (2, 3, 11)

Futás közben a Készlet és Készlet törlődik, és mindkettőjüket egyszerűnek látjuk Készletek. Tehát annak ellenére, hogy futás közben tökéletesen meg lehet állapítani, hogy ez az érték a Készlet, nem tudjuk megmondani, hogy a Készlet karakterláncok, egész számok vagy valami más: ezt az információt törölték.

Tehát hogyan akadályozza meg Kotlin fordítója, hogy hozzáadjunk egy a-t Nem húros ba be Készlet? Vagy amikor egy elemet kapunk az a-ból Készlet, honnan tudja, hogy az elem a Húr?

A válasz egyszerű. A fordító felelős a típusinformációk törléséért de előtte valójában tudja a könyveket változó tartalmazza Húr elemek.

Tehát, valahányszor kapunk belőle egy elemet, a fordító a Húr vagy amikor hozzáadunk egy elemet, a fordító beírja a bemenetet.

6.2. Megerősített típusparaméterek

Érezzük jól magunkat a generikusokkal, és hozzunk létre egy kiterjesztési funkciót a szűréshez Gyűjtemény elemek típusuk alapján:

fun Iterable.filterIsInstance () = filter {it is T} Hiba: Nem lehet ellenőrizni például a törölt típust: T

A "ez T ” rész, minden gyűjteményelemnél ellenőrzi, hogy az elem típus típusú-e T, de mivel a típusinformációk futás közben törlődtek, így nem tudunk reflektálni a típusparaméterekre.

Vagy lehet?

A típusú törlési szabály általában igaz, de egy esetben elkerülhetjük ezt a korlátozást: Inline funkciók. Az inline függvények típusparaméterei lehetnek újraszerződött, tehát futás közben hivatkozhatunk azokra a típusú paraméterekre.

Az inline funkciók teste be van vonva. Vagyis a fordító a testet közvetlenül olyan helyekre helyettesíti, ahol a függvény meghívásra kerül a normál függvényhívás helyett.

Ha az előző függvényt deklaráljuk Sorban és jelölje meg a type paramétert újraszerződött, akkor futás közben hozzáférhetünk az általános típusú információkhoz:

inline fun Iterable.filterIsInstance () = szűrő {ez T}

A beillesztett varázslat varázslatként működik:

>> val set = setOf ("1984", 2, 3, "Brave new world", 11) >> println (set.filterIsInstance ()) [2, 3, 11]

Írjunk még egy példát. Mindannyian ismerjük azokat a tipikus SLF4j-t Logger definíciók:

class User {privát val log = LoggerFactory.getLogger (Felhasználó :: class.java) // ...}

Az újrafogalmazott soros függvények használatával elegánsabban és kevésbé szintaxistól borzasztóan írhatunk Logger definíciók:

inline fun logger (): Logger = LoggerFactory.getLogger (T :: class.java)

Akkor írhatunk:

osztály Felhasználó {private val log = logger () // ...}

Ez tisztább lehetőséget kínál a fakitermelés megvalósítására, a Kotlin-módszerrel.

6.3. Mélyen elmerül az Inline Reification

Szóval, mi a különlegesség az inline függvényekben, hogy a típus újrareprezentálása csak velük működjön? Mint tudjuk, Kotlin fordítója másolja az inline függvények byte-kódját olyan helyekre, ahol a függvényt meghívják.

Mivel minden fordulóban a fordító tudja a pontos paramétertípust, lecserélheti az általános típusú paramétert a tényleges típusreferenciákkal.

Például, amikor azt írjuk:

osztály Felhasználó {private val log = logger () // ...}

Amikor a fordító behúzza a naplózó () függvényhívás, tudja a tényleges általános típusú paramétert -Felhasználó. Tehát a fordító a típusinformációk törlése helyett kihasználja az újrapróbálkozás lehetőségét, és újraminősíti a tényleges típusparamétert.

7. Következtetés

Ebben a cikkben a Kotlin Generic típusokat vizsgáltuk. Láttuk, hogyan kell használni a ki és ban ben kulcsszavakat megfelelően. Típus vetületeket használtunk, és meghatároztunk egy általános módszert, amely általános megszorításokat használ.

Ezeknek a példáknak és kódrészleteknek a megvalósítása megtalálható a GitHub projektben - ez egy Maven projekt, ezért könnyen importálhatónak és futtathatónak kell lennie.


$config[zx-auto] not found$config[zx-overlay] not found