Funkcje wyższego rzędu są bardzo pomocne i mogą naprawdę poprawić reusability
kod. Jednak jednym z największych problemów związanych z ich używaniem jest wydajność. Wyrażenia lambda są kompilowane do klas (często klas anonimowych), a tworzenie obiektów w Javie to ciężka operacja. Nadal możemy efektywnie korzystać z funkcji wyższego rzędu, zachowując wszystkie korzyści, dzięki wbudowaniu funkcji.
tu pojawia się funkcja inline w obraz
Gdy funkcja jest oznaczona jako inline
, podczas kompilacji kodu kompilator zastąpi wszystkie wywołania funkcji rzeczywistą treścią funkcji. Ponadto wyrażenia lambda podane jako argumenty są zastępowane ich rzeczywistą treścią. Nie będą traktowane jako funkcje, ale jako rzeczywisty kod.
W skrócie: - Inline -> zamiast być wywoływane, są zastępowane przez kod funkcji w czasie kompilacji ...
W Kotlinie użycie funkcji jako parametru innej funkcji (tzw. Funkcji wyższego rzędu) wydaje się bardziej naturalne niż w Javie.
Używanie lambd ma jednak pewne wady. Ponieważ są to klasy anonimowe (a zatem obiekty), potrzebują pamięci (a nawet mogą zwiększyć ogólną liczbę metod Twojej aplikacji). Aby tego uniknąć, możemy wprowadzić nasze metody.
fun notInlined(getString: () -> String?) = println(getString())
inline fun inlined(getString: () -> String?) = println(getString())
Z powyższego przykładu : - Te dwie funkcje robią dokładnie to samo - wypisują wynik funkcji getString. Jedna jest wstawiona, a druga nie.
Jeśli sprawdzisz zdekompilowany kod java, zobaczysz, że metody są całkowicie identyczne. Dzieje się tak, ponieważ słowo kluczowe inline jest instrukcją dla kompilatora, aby skopiować kod do witryny wywołania.
Jeśli jednak przekazujemy dowolny typ funkcji do innej funkcji, jak poniżej:
//Compile time error… Illegal usage of inline function type ftOne...
inline fun Int.doSomething(y: Int, ftOne: Int.(Int) -> Int, ftTwo: (Int) -> Int) {
//passing a function type to another function
val funOne = someFunction(ftOne)
/*...*/
}
Aby rozwiązać ten problem, możemy przepisać naszą funkcję jak poniżej:
inline fun Int.doSomething(y: Int, noinline ftOne: Int.(Int) -> Int, ftTwo: (Int) -> Int) {
//passing a function type to another function
val funOne = someFunction(ftOne)
/*...*/}
Załóżmy, że mamy funkcję wyższego rzędu, jak poniżej:
inline fun Int.doSomething(y: Int, noinline ftOne: Int.(Int) -> Int) {
//passing a function type to another function
val funOne = someFunction(ftOne)
/*...*/}
W tym przypadku kompilator powie nam, abyśmy nie używali słowa kluczowego inline, gdy jest tylko jeden parametr lambda i przekazujemy go do innej funkcji. Możemy więc przepisać powyższą funkcję jak poniżej:
fun Int.doSomething(y: Int, ftOne: Int.(Int) -> Int) {
//passing a function type to another function
val funOne = someFunction(ftOne)
/*...*/
}
Uwaga : -Musieliśmy również usunąć słowo kluczowe noinline, ponieważ może być używane tylko do funkcji wbudowanych!
Załóżmy, że mamy taką funkcję ->
fun intercept() {
// ...
val start = SystemClock.elapsedRealtime()
val result = doSomethingWeWantToMeasure()
val duration = SystemClock.elapsedRealtime() - start
log(duration)
// ...}
Działa to dobrze, ale logika funkcji jest zanieczyszczona kodem pomiarowym, co utrudnia kolegom pracę nad tym, co się dzieje. :)
Oto jak funkcja wbudowana może pomóc w tym kodzie:
fun intercept() {
// ...
val result = measure { doSomethingWeWantToMeasure() }
// ...
}
}
inline fun <T> measure(action: () -> T) {
val start = SystemClock.elapsedRealtime()
val result = action()
val duration = SystemClock.elapsedRealtime() - start
log(duration)
return result
}
Teraz mogę skoncentrować się na czytaniu, co jest głównym celem funkcji intercept () bez pomijania linii kodu pomiarowego. Korzystamy również z możliwości ponownego wykorzystania tego kodu w innych miejscach, w których chcemy
inline pozwala na wywołanie funkcji z argumentem lambda w zamknięciu ({...}) zamiast przekazywania miary podobnej do lambda (myLamda)
Kiedy jest to przydatne?
Słowo kluczowe inline jest przydatne w przypadku funkcji, które akceptują inne funkcje lub wyrażenia lambda jako argumenty.
Bez słowa kluczowego inline w funkcji argument lambda tej funkcji jest konwertowany w czasie kompilacji na wystąpienie interfejsu funkcji za pomocą jednej metody o nazwie invoke (), a kod w wyrażeniu lambda jest wykonywany przez wywołanie metody invoke () w tej instancji funkcji wewnątrz treści funkcji.
W przypadku słowa kluczowego wbudowanego w funkcji ta konwersja podczas kompilacji nigdy nie występuje. Zamiast tego treść funkcji wbudowanej zostaje wstawiona w miejscu wywołania, a jej kod jest wykonywany bez narzutu związanego z tworzeniem instancji funkcji.
Hmmm? Przykład w Androidzie ->
Powiedzmy, że mamy funkcję w klasie routera aktywności, która uruchamia działanie i stosuje pewne dodatki
fun startActivity(context: Context,
activity: Class<*>,
applyExtras: (intent: Intent) -> Unit) {
val intent = Intent(context, activity)
applyExtras(intent)
context.startActivity(intent)
}
Ta funkcja tworzy intencję, stosuje pewne dodatki, wywołując argument funkcji applyExtras i rozpoczyna działanie.
Jeśli spojrzymy na skompilowany kod bajtowy i zdekompilujemy go na Javę, wygląda to mniej więcej tak:
void startActivity(Context context,
Class activity,
Function1 applyExtras) {
Intent intent = new Intent(context, activity);
applyExtras.invoke(intent);
context.startActivity(intent);
}
Powiedzmy, że nazywamy to z poziomu odbiornika kliknięć w działaniu:
override fun onClick(v: View) {
router.startActivity(this, SomeActivity::class.java) { intent ->
intent.putExtra("key1", "value1")
intent.putExtra("key2", 5)
}
}
Zdekompilowany kod bajtowy tego odbiornika kliknięć wyglądałby wtedy następująco:
@Override void onClick(View v) {
router.startActivity(this, SomeActivity.class, new Function1() {
@Override void invoke(Intent intent) {
intent.putExtra("key1", "value1");
intent.putExtra("key2", 5);
}
}
}
Nowa instancja funkcji Function1 jest tworzona za każdym razem, gdy jest uruchamiany odbiornik kliknięć. To działa dobrze, ale nie jest idealne!
Teraz po prostu dodajmy inline do naszej metody routera aktywności:
inline fun startActivity(context: Context,
activity: Class<*>,
applyExtras: (intent: Intent) -> Unit) {
val intent = Intent(context, activity)
applyExtras(intent)
context.startActivity(intent)
}
Bez zmiany naszego kodu odbiornika kliknięć możemy teraz uniknąć tworzenia tej instancji Function1. Odpowiednik Java kodu nasłuchiwania kliknięć wyglądałby teraz mniej więcej tak:
@Override void onClick(View v) {
Intent intent = new Intent(context, SomeActivity.class);
intent.putExtra("key1", "value1");
intent.putExtra("key2", 5);
context.startActivity(intent);
}
Otóż to.. :)
Umieszczanie funkcji w tekście oznacza po prostu skopiowanie jej treści i wklejenie w miejscu wywołania funkcji. Dzieje się to w czasie kompilacji.