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:
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.
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.
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.
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!
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 ()
șiexpulza()
. 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 elevuluiDupă 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 extensieexpel (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 ()
deClassB
tip în interiorul unei alte claseClasa 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 tipClassB
când este apelat în interiorul funcției de extensieexFunction ()
. Pentru ca noi să invocămtoString ()
metodă a receptorul de expediereClasa a
în schimb, trebuie să folosim un calificatacest
:// ... 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ă. FunctiacircleOperation ()
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ă) returInvocarea 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 pentrucircleOperation ()
, 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.09734155810323Utilizarea 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 * factorAici
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.2Pentru 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ă parametrullungime
a funcției exterioareprintFilteredNamesByLength ()
. Rețineți că acest parametru este definit în afara domeniului de aplicare al lambda, dar că lambda este în continuare capabil să accesezelungime
. 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 cuin 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 arein linie
modificator. Acum, să vedem octetul de la Kotlin generat când compilam și decompilam codul și apoi îl comparăm cu unul care arein 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 interiorulprincipal()
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 dedistracţ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 apelacircleOperation ()
, a copiat acumcircleOperation ()
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()
șiprimul()
, veți vedea că ei auin linie
modificator și sunt, de asemenea, mici în dimensiune.distracție publică inlineIterable .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
ModificatorulDacă 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țiiAici, am inserat
noinline
modificator la al doilea parametru lambda. Rețineți că acest modificator este valabil numai dacă funcția arein 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+!