Kotlin de la zero funcții avansate

Kotlin este un limbaj funcțional, ceea ce înseamnă că funcțiile sunt în față și centru. Limba este dotată cu funcții pentru a face funcțiile de codare ușor și expresiv. În acest post, veți afla despre funcțiile extensiei, funcțiile de înaltă ordine, funcțiile de închidere și funcțiile inline din Kotlin.

În articolul precedent, ați aflat despre funcțiile de nivel superior, expresiile lambda, funcțiile anonime, funcțiile locale, funcțiile infix și, în final, funcțiile membrilor din Kotlin. În acest tutorial, vom continua să aflăm mai multe despre funcțiile din Kotlin prin săparea în:

  • funcții de extensie 
  • funcții de ordin superior
  • închiderile
  • funcții inline

1. Funcții de extensie 

Nu ar fi frumos dacă Şir tastați în Java a avut o metodă de a capitaliza prima literă într-o Şir-ca ucfirst () în PHP? Am putea numi această metodă upperCaseFirstLetter ()

Pentru a realiza acest lucru, puteți crea o Şir subclasa care extinde Şir tastați în Java. Dar amintiți-vă că Şir clasa în Java este finală - ceea ce înseamnă că nu o puteți extinde. O soluție posibilă pentru Kotlin ar fi să creeze funcții de ajutor sau funcții de nivel superior, dar acest lucru ar putea să nu fie ideal deoarece nu am putut folosi funcția IDE auto-completare pentru a vedea lista metodelor disponibile pentru Şir tip. Ce ar fi frumos ar fi să adăugați cumva o funcție unei clase fără a trebui să moșteniți din acea clasă.

Ei bine, Kotlin ne-a acoperit cu o altă caracteristică minunată: funcții de extensie. Acestea ne dau posibilitatea de a extinde o clasă cu noi funcționalități fără a trebui să moștenim din acea clasă. Cu alte cuvinte, nu este nevoie să creați un subtip nou sau să modificați tipul original. 

O funcție de extindere este declarată în afara clasei pe care dorește să o extindă. Cu alte cuvinte, este și o funcție de nivel superior (dacă doriți o reîmprospătare la funcțiile de nivel superior din Kotlin, vizitați tutorialul Mai Multe Cu Funcții din această serie). 

Împreună cu funcțiile de extensie, Kotlin susține de asemenea proprietăți de extindere. În acest post, vom discuta despre funcțiile de extensie și vom aștepta până la o postare viitoare pentru a discuta despre proprietățile extensiei împreună cu clasele din Kotlin. 

Crearea unei funcții de extensie

După cum puteți vedea în codul de mai jos, am definit o funcție de nivel superior ca fiind normal pentru a declara o funcție de extensie. Această funcție de extindere se află în interiorul unui pachet numit com.chike.kotlin.strings

Pentru a crea o funcție de extensie, trebuie să prefixați numele clasei pe care o extindeți înainte de numele funcției. Numele clasei sau tipul pe care este definită extensia se numește tipul receptorului, si obiect receptor este instanța de clasă sau valoarea pe care se numește funcția extensie.

pachet com.chike.kotlin.strings distracție String.upperCaseFirstLetter (): String returnați acest.substring (0, 1) .toUpperCase () plus (this.substring (1))

Rețineți că acest cuvântul cheie din interiorul corpului funcției face trimitere la obiectul sau instanța receptorului. 

Apelarea unei funcții de extensie

După crearea funcției dvs. de extensie, va trebui mai întâi să importați funcția extensie în alte pachete sau fișiere care vor fi utilizate în acel fișier sau pachet. Apoi, apelarea funcției este la fel ca apelarea oricărei alte metode a clasei de tip receptor.

pachet com.chike.kotlin.packagex import com.chike.kotlin.strings.upperCaseFirstLetter print ("chike" .upperCaseFirstLetter ()) // "Chike"

În exemplul de mai sus, tipul receptorului este clasa Şir, si obiect receptor este "Chike". Dacă utilizați un IDE, cum ar fi IntelliJ IDEA, care are caracteristica IntelliSense, veți vedea noua funcție de extindere sugerată printre lista altor funcții într-o Şir tip. 

Interoperabilitate Java

Rețineți că în spatele scenei, Kotlin va crea o metodă statică. Prima argumentare a metodei statice este obiectul receptorului. Deci este ușor pentru apelanții Java să numească această metodă statică și apoi să treacă obiectul receptorului ca argument. 

De exemplu, dacă funcția de extensie a fost declarată într-o StringUtils.kt fișierul, compilatorul Kotlin ar crea o clasă Java StringUtilsKt cu o metodă statică upperCaseFirstLetter ()

/ * Java * / pachet com.chike.kotlin.strings public class StringUtilsKt public static String upperCaseFirstLetter (String str) return str.substring (0, 1) .toUpperCase () + str.substring (1); 

Acest lucru înseamnă că apelanții Java pot apela pur și simplu metoda prin referirea la clasa generată, la fel ca pentru orice altă metodă statică. 

/ * Java * / imprimare (StringUtilsKt.upperCaseFirstLetter ("chike")); // "Chike"

Rețineți că acest mecanism Java interop este similar cu modul în care funcțiile de nivel superior funcționează în Kotlin, așa cum am discutat în postul More Fun With Functions!

Funcții de extensie vs. funcții ale membrilor

Rețineți că funcțiile extensiei nu pot suprascrie funcțiile deja declarate într-o clasă sau într-o interfață cunoscută sub numele de funcții membre (dacă doriți o actualizare a funcțiilor membrilor din Kotlin, aruncați o privire la tutorialul anterior din această serie). Deci, dacă ați definit o funcție de extensie cu exact aceeași semnătură de funcții - același nume de funcție și același număr, tipuri și ordine de argumente, indiferent de tipul returnării - compilatorul Kotlin nu o va invoca. În procesul de compilare, atunci când o funcție este invocată, compilatorul Kotlin va căuta mai întâi o potrivire în funcțiile membre definite în tipul instanței sau în superclasele sale. Dacă există o potrivire, atunci acea funcție membră este cea invocată sau legată. Dacă nu există nici o potrivire, atunci compilatorul va invoca orice funcție de extindere a acelui tip. 

Deci, în rezumat: funcțiile membrilor întotdeauna câștigă. 

Să vedem un exemplu practic.

În codul de mai sus, am definit un tip numit Student cu două funcții ale membrilor: printResult () și expulza(). Am definit apoi două funcții de extensie care au aceleași nume ca și funcțiile membrilor. 

Să sunăm printResult () funcția și a vedea rezultatul. 

val student = Student () student.printResult () // Imprimarea rezultatului elevului

După cum puteți vedea, funcția invocată sau legată a fost funcția membrului și nu funcția de extensie cu aceeași semnătură de funcție (deși IntelliJ IDEA vă va da încă o sugestie).

Cu toate acestea, sunând la funcția membru expulza() și funcția extensie expel (motiv: String) va produce rezultate diferite deoarece semnăturile funcțiilor sunt diferite. 

student.expel () // Expulzând elevul de la student.expel ("furat banii") // Expulzând elevul de la Școală. Motivul: "a furat bani"

Membrii funcțiilor de extensie

În majoritatea timpului, veți declara o funcție de extindere ca o funcție de nivel superior, dar rețineți că puteți, de asemenea, să le declarați funcții membre. 

Clasa ClasaB Clasa Clasa B Functia ClassB.exFunction () print (toString ()) // apeleaza ClassB toString () Functia callExFunction (classB: ClassB) classB.exFunction () // apeleaza functia extensie

În codul de mai sus, am declarat o funcție de extensie exFunction () de ClassB tip în interiorul unei alte clase Clasa a. receptorul de expediere este instanța clasei în care extensia este declarată, iar instanța tipului de receptor al metodei extensiei se numește receptor de extensie. Când există un conflict de nume sau o umbră între receptorul de expediere și receptorul de extensie, rețineți că compilatorul alege receptorul de extensie. 

Deci, în exemplul de cod de mai sus, receptor de extensie este un exemplu de ClassB-deci înseamnă că toString () metoda este de tip ClassB când este apelat în interiorul funcției de extensie exFunction (). Pentru ca noi să invocăm toString () metodă a receptorul de expediere Clasa a în schimb, trebuie să folosim un calificat acest:

// ... fun ClassB.extFunction () print ([email protected] ()) // apelează acum metoda ClassA toString () // ... 

2. Funcții de ordin superior 

O funcție de ordin superior este doar o funcție care ia ca parametru o altă funcție (sau expresia lambda), returnează o funcție sau ambele. ultimul() funcția de colectare este un exemplu de funcție de ordin superior din biblioteca standard. 

val stringList: Listă = listOf ("in", "cel", "clubul") print (stringList.last it.length == 3) // "

Aici am trecut o lambda la ultimul funcția de a servi ca predicat pentru a căuta într-un subset de elemente. Acum ne vom arunca cu capul în crearea funcțiilor noastre de ordin superior din Kotlin. 

Crearea unei funcții de comandă superioară

Privind funcția circleOperation () mai jos, are doi parametri. Primul, rază, acceptă un dublu, iar cel de-al doilea, op, este o funcție care acceptă un dublu ca intrare și, de asemenea, returnează un dublu ca ieșire - putem spune mai succint că al doilea parametru este "o funcție de la dublu la dublu". 

Observați că op funcțiile tipurilor de parametri funcționale sunt înfășurate în paranteze (), iar tipul de ieșire este separat de o săgeată. Functia circleOperation () este un exemplu tipic al unei funcții de ordin superior care acceptă o funcție ca parametru.

distracție calCreație (rază: dublă) = (2 * Math.PI) * radius distracție calArea (rază: dublă): Double = (Math.PI) * Math.pow (radius, 2.0) (Dublu) -> Dublu): Dublu val rezultat = rezultat op (rază) retur

Invocarea unei funcții de ordin superior

În invocarea acestui lucru circleOperation () funcția, trecem o altă funcție, calArea (), să-l. (Rețineți că dacă semnătura metodei funcției trecute nu corespunde cu ceea ce declară funcția de comandă de nivel superior, apelul funcției nu se va compila.) 

Pentru a trece calArea () funcția ca parametru pentru circleOperation (), trebuie să-l prefixăm :: și omiteți () paranteze.

(cercOperație (3.0, :: calArea)) // 28.274333882308138 print (circleOperation (3.0, calArea)) // nu compilați imprimarea (circleOperation (3.0, calArea ()) // nu compilați print (circleOperation 6.7, :: calCircumference)) // 42.09734155810323

Utilizarea cu înțelepciune a funcțiilor de înaltă ordine poate face codul nostru mai ușor de citit și mai ușor de înțeles. 

Lambda și funcțiile de ordin superior

Putem de asemenea să transmitem o lambda (sau o funcție literală) unei funcții de ordin superior direct atunci când invocăm funcția: 

circleOperație (5.3, (2 * Math.PI) * it)

Amintiți-vă, pentru a evita să numim explicit argumentul, putem folosi aceasta argument numele auto-generat pentru noi numai în cazul în care lambda are un argument. (Dacă doriți o reîmprospătare pe lambda în Kotlin, vizitați tutorialul More Fun With Functions din această serie).

Întoarcerea unei funcții

Rețineți că, în plus față de acceptarea unei funcții ca parametru, funcțiile de ordin superior pot, de asemenea, să returneze o funcție apelanților. 

multiplicator distractiv (factor: dublu): (dublu) -> dublu = număr -> număr * factor

Aici multiplicator () funcția va returna o funcție care aplică factorul dat la orice număr trecut în el. Această funcție returnată este o literă lambda (sau funcția literală) de la dublu la dublu (adică paramul de intrare al funcției returnate este un tip dublu, iar rezultatul de ieșire este, de asemenea, un tip dublu).  

val duplicator = multiplicator (2) print (dublu (5.6)) // 11.2

Pentru a testa acest lucru, am trecut printr-un factor de doi și am atribuit funcția returnată la variabila doubler. Putem invoca acest lucru ca o funcție normală și orice valoare pe care o vom trece în ea va fi dublată.

3. închideri

O închidere este o funcție care are acces la variabile și parametri care sunt definiți într-un domeniu exterior. 

distracție printFilteredNamesByLength (lungime: int) val names = arrayListOf ("Adam", "Andrew", "Chike", "Kechi") val filterResult = nume.filter it.length == lengthln println (filterResult) printFilteredNamesByLength 5) // [Chike, Kechi]

În codul de mai sus, lambda a trecut la filtru() funcția de colectare utilizează parametrul lungime a funcției exterioare printFilteredNamesByLength (). Rețineți că acest parametru este definit în afara domeniului de aplicare al lambda, dar că lambda este în continuare capabil să acceseze lungime. Acest mecanism este un exemplu de închidere în programarea funcțională.

4. Funcții în linie

În funcția Mai multe funcții cu funcții, am menționat că compilatorul Kotlin creează o clasă anonimă în versiunile anterioare de Java în spatele scenei atunci când creează expresii lambda. 

Din nefericire, acest mecanism introduce overhead deoarece o clasă anonimă este creată sub capotă de fiecare dată când creăm o lambda. De asemenea, o lambda care utilizează parametrul funcției exterioare sau variabila locală cu o închidere adaugă propria sa alocare de memorie, deoarece un nou obiect este alocat heapului cu fiecare invocare. 

Compararea funcțiilor inline cu funcții normale

Pentru a preveni aceste cheltuieli generale, echipa Kotlin ne-a oferit in linie modificator pentru funcții. O funcție de ordin superior cu in linie Modificatorul va fi înlăturat în timpul compilării codului. Cu alte cuvinte, compilatorul va copia lambda (sau funcția literală), precum și corpul funcției de ordin superior și le va lipi la site-ul de apel. 

Să examinăm un exemplu practic. 

distracție (rază: dublă, op: (dublă) -> dublă) println ("Radius este $ radius") val rezultat = op (radius) println) circleOperație (5.3, (2 * Math.PI) * it 

În codul de mai sus, avem o funcție de ordin superior circleOperation () care nu are in linie modificator. Acum, să vedem octetul de la Kotlin generat când compilam și decompilam codul și apoi îl comparăm cu unul care are in linie modificator. 

clasa publica finala InlineFunctionKt cerc static final public staticOperatie (raza dubla, @NotNull Function1 op) Intrinsics.checkParameterIsNotNull (op, "op"); String var3 = "Radius is" + rază; System.out.println (var3); rezultat dublu = ((Număr) op.invoke (radius)) doubleValue (); String var5 = "Rezultatul este" + rezultat; System.out.println (var5);  public static final void principal (@NotNull String [] args) Intrinsics.checkParameterIsNotNull (args, "args"); cercoperire (5.3D, (funcția1) null.INSTANCE); 

În Java bytecode generat mai sus, puteți vedea că compilatorul a numit funcția circleOperation () în interiorul principal() metodă.

Să specificăm acum funcția de ordin superior ca in linie în schimb, și de a vedea, de asemenea, octetul generat.

("Radiusul este $ radius") val rezultat = op (radius) println ("Rezultatul este $ result") fun main (args: mulțime) circleOperație (5.3, (2 * Math.PI) * it

Pentru a face o funcție de ordin superior în linie, trebuie să inserăm funcția in linie modificator înainte de distracţie cuvânt cheie, la fel ca în codul de mai sus. Să verificăm, de asemenea, octetul generat pentru această funcție inline. 

statica publica statica finala voidOperatie (raza dubla, @NotNull Function1 op) Intrinsics.checkParameterIsNotNull (op, "op"); String var4 = "Radius is" + rază; System.out.println (Var4); rezultat dublu = ((Număr) op.invoke (radius)) doubleValue (); String var6 = "Rezultatul este" + rezultat; System.out.println (var6);  public static final void principal (@NotNull String [] args) Intrinsics.checkParameterIsNotNull (args, "args"); raza dublă $ iv = 5.3D; String var3 = "Radius este" + raza $ iv; System.out.println (var3); rezultatul dublu $ iv = 6.283185307179586D * raza $ iv; String var9 = "Rezultatul este" + rezultat $ iv; System.out.println (var9); 

Privind la baia generată pentru funcția inline din interior principal() funcția, puteți observa că în loc de a apela circleOperation () , a copiat acum circleOperation () funcția corpul, inclusiv corpul lambda și lipit-o la locul său de apel.

Cu acest mecanism, codul nostru a fost optimizat în mod semnificativ - nu mai există crearea de clase anonime sau alocări suplimentare de memorie. Dar fii foarte conștient de faptul că vom avea un ocol mai mare în spatele scenei decât înainte. Din acest motiv, este recomandat să se introducă numai funcții de ordin mai mic, care acceptă lambda ca parametri. 

Multe dintre funcțiile standard de bibliotecă standard de la Kotlin au modificatorul inline. De exemplu, dacă luați o privire asupra funcțiilor funcției de colectare filtru() și primul(), veți vedea că ei au in linie modificator și sunt, de asemenea, mici în dimensiune. 

distracție publică inline  Iterable.filtru (predicat: (T) -> Boolean): Listă return filterTo (ArrayList(), predicat) distracție publică inline  Iterable.primul (predicat: (T) -> Boolean): T pentru (element în acest) dacă (predicate (element)) arunca elementul NoSuchElementException

Nu uitați să nu introduceți funcții normale care nu acceptă o lambda ca parametru! Ei vor compila, dar nu ar exista nici o îmbunătățire semnificativă a performanței (IntelliJ IDEA ar da chiar și un indiciu despre acest lucru).  

noinline Modificatorul

Dacă aveți mai mult de doi parametri lambda într-o funcție, aveți opțiunea de a decide care lambda să nu fie inline folosind noinline modificator al parametrului. Această funcționalitate este utilă mai ales pentru un parametru lambda care va lua multă cod. Cu alte cuvinte, compilatorul Kotlin nu va copia și lipi acel lambda unde este chemat, ci va crea o clasă anonimă în spatele scenei.  

inline distracție myFunc (op: (dublu) -> dublu, noinline op2: (Int) -> Int) // efectuați operații

Aici, am inserat noinline modificator la al doilea parametru lambda. Rețineți că acest modificator este valabil numai dacă funcția are in linie modificator.

Stack Trace în funcții inline

Rețineți că atunci când o excepție este aruncată în interiorul unei funcții inline, stiva de apel a metodei din traseul stivei diferă de o funcție normală fără in linie modificator. Acest lucru se datorează mecanismului de copiere și lipire folosit de compilator pentru funcții inline. Lucrul interesant este că IntelliJ IDEA ne ajută să navigăm cu ușurință în stivă de apeluri metodice din traseul stivei pentru o funcție inline. Să vedem un exemplu.

inline distracție myFunc (opțiune (dublă) -> dublu) arunca excepția ("mesajul 123") fun principal (args: Array) myFunc (4.5)

În codul de mai sus, o excepție este aruncată deliberat în interiorul funcției inline myFunc (). Să vedem acum urmărirea stivei în IntelliJ IDEA când se execută codul. Privind la captura de ecran de mai jos, puteți vedea că ni se oferă două opțiuni de navigare pentru a alege: corpul funcției Inline sau site-ul de apel funcțional inline. Alegerea primului ne va duce la punctul în care excepția a fost aruncată în corpul funcției, în timp ce aceasta din urmă ne va duce până la punctul în care a fost numită metoda.

În cazul în care funcția nu a fost una funcțională, urmărirea stivei ar fi ca cea pe care ați putea fi deja familiarizată:

Concluzie

În acest tutorial, ați învățat și mai multe lucruri pe care le puteți face cu funcțiile din Kotlin. Am acoperit:

  • funcții de extensie
  • funcții de ordin superior
  • închiderile
  • funcții inline

În următorul tutorial din seria Kotlin From Scratch vom începe o programare orientată pe obiecte și vom începe să învățăm cum funcționează clasele în Kotlin. Ne vedem în curând!

Pentru a afla mai multe despre limba Kotlin, vă recomand să vizitați documentația Kotlin. Sau verificați câteva dintre celelalte postări pentru dezvoltarea aplicațiilor Android aici pe Envato Tuts+!

Cod