programowanieMiddle Kotlin Developer

Czym są funkcje zakresu w Kotlinie (let, also, run, apply, with)? Jakie są różnice między tymi funkcjami, jak je wybierać do różnych zadań, jakie subtelności mogą pojawić się podczas ich używania? Podaj przykłady.

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź.

Funkcje zakresu ("funkcje zakresowe") w Kotlinie to standardowe funkcje (let, also, run, apply, with), które pozwalają zarządzać kontekstem wykonania bloku kodu dla obiektu. Różnią się:

  • typem wartości zwracanej,
  • tym, jak obiekt jest dostępny wewnątrz bloku: przez it lub przez this.

Krótkie porównanie:

Funkcjathis/itZwracaDo czego?
letitrezultatłańcuch operacji, praca z nullable, mapowanie
alsoitobiektefekty uboczne, logowanie, debugowanie
runthisrezultatobliczenia, inicjalizacja z wartością zwracaną
applythisobiektkonfiguracja obiektu, budowniczowie
withthisrezultatpraca z zewnętrznymi API, obiekt „na zewnątrz”

Przykłady:

  • let: wygodne, gdy obiekt jest nullable:
val str: String? = "Text" str?.let { println(it.length) }
  • apply: konfiguracja obiektu:
val paint = Paint().apply { color = Color.RED strokeWidth = 2f }
  • run: wykonanie na obiekcie, zwraca wynik:
val length = "abcde".run { length }
  • with: do pracy z zewnętrznym obiektem:
val sb = StringBuilder() with(sb) { append("Hello, ") append("world!") toString() }
  • also: do efektów ubocznych (np. logi):
val list = mutableListOf(1, 2, 3) list.also { println("Before: $it") }.add(4)

Na co zwrócić uwagę:

  • let tworzy kopię obiektu w it, zmienianie właściwości obiektu nie jest zbyt wygodne.
  • apply i also zawsze zwracają ten sam obiekt (this / it), co jest przydatne dla budowniczych.
  • run/with często mylone: with to zwykła funkcja, nie extension.

Pytanie z pułapką.

Jaka jest różnica między let a also?

Odpowiedź:

  • Oba używają it wewnątrz bloku,
  • let zwraca wynik lambdy, często używane do łańcuchów transformacji,
  • also zwraca oryginalny obiekt, używane do efektów ubocznych (log, debug), aby nie zakłócać łańcucha przekształceń.

Przykład:

val result = listOf(1).also { println(it) }.map { it * 2 } // wynik — List<Int>

Przykłady rzeczywistych błędów z powodu nieznajomości subtelności tematu:


Historia

Nowicjusz użył let do konfiguracji obiektu, sądząc, że można w ten sposób zmienić jego stan „w łańcuchu”. W rezultacie po zakończeniu bloku konfiguracji otrzymał nie obiekt, lecz wynik lambdy (na przykład nic), co zakłóciło łańcuch budowania DSL.


Historia

Podczas pisania kodu do pracy z obiektami nullable, użyto run zamiast let, nie zauważając różnicy w zwracanej wartości. W rezultacie wynik wyrażenia różnił się od oczekiwanego, pojawiały się null, gdzie nie powinno ich być — logika aplikacji została złamana.


Historia

W dużym budowniczym przypadkowo użyto with dla obiektów wewnętrznych, licząc na wzorzec extension. Ponieważ with nie jest funkcją extension, łańcuch kilku bloków with działał nieprawidłowo, wewnętrzne wywołania się myliły i przekraczały zakres aktualnego obiektu. Konieczne było całkowite przepisanie hierarchii tworzenia obiektów.