Moc pętli for w Kotlinie
To jest rozdział z książki Kotlin Essentials.
W Javie i innych starszych językach pętla for zwykle ma trzy części: pierwsza inicjuje zmienną przed rozpoczęciem pętli; druga zawiera warunek wykonania bloku kodu; trzecia jest wykonywana po bloku kodu.
// pętla for w Javie
for(int i=0; i < 5; i++){
System.out.println(i);
}
Taki zapis jest jednak dość skomplikowany i łatwo w nim o błąd. Wyobraź sobie, że ktoś przypadkiem użyje >
lub <=
zamiast <
. Taka mała różnica nie jest łatwa do zauważenia, ale istotnie wpływa na zachowanie tej pętli for.
Zamiast klasycznej pętli for, wiele języków wprowadziło nowocześniejszą alternatywę zaprojektowaną do iterowania po kolekcjach. Dlatego w językach takich jak Java czy JavaScript mamy dwie zupełnie różne rodzaje pętli for, obie zdefiniowane tym samym słowem kluczowym. W Kotlinie jest prościej, mamy jedną uniwersalną pętlę for, która może być używana do iterowania po kolekcji, mapie, zakresie liczb i wielu innych.
Ogólnie rzecz biorąc, pętla for jest używana w Kotlinie do iterowania po czymś, co jest iterowalne1.
Możemy iterować po listach lub zbiorach.
Możemy również iterować po dowolnym innym obiekcie, o ile zawiera metodę iterator
bez parametrów i z modyfikatorem operator
, zwracającej typ Iterator
. Ten warunek jest spełniony przez wszystkie klasy implementujące interfejs Iterable
.
Wnioskowany typ zmiennej zdefiniowanej wewnątrz pętli for pochodzi z argumentu typu Iterable
. Gdy iterujemy po Iterable<User>
, wnioskowany typ elementu to User
. Gdy iterujemy po Iterable<Long?>
, wnioskowany typ elementu to Long?
. To samo dotyczy wszystkich innych typów.
Ten mechanizm, oparty na Iterable
, jest naprawdę potężny i pozwala nam pokryć liczne przypadki użycia, z których jednym z najpopularniejszych jest wykorzystanie zakresów.
Zakresy
Jeśli umieścisz dwie kropki pomiędzy dwiema liczbami, jak w 1..5
, utworzysz IntRange
, czyli zakres liczb od 1 do 5. IntRange
implementuje Iterable<Int>
, więc możemy jej użyć w pętli for:
To rozwiązanie jest nie tylko wygodne, ale również wydajne, ponieważ kompilator Kotlina optymalizuje je do efektywnej iteracji po numerach (a więc jeśli nie jest to naprawdę konieczne, obiekt klasy IntRange
nie powstaje).
Zakresy utworzone za pomocą ..
obejmują ostatnią wartość (co oznacza, że są to zamknięte zakresy). Jeśli chcesz zakresu, który kończy się przed ostatnią wartością, użyj zamiast tego ..<
lub funkcji infiksowej until
.
Zarówno ..
, jak i ..<
zaczynają się od wartości po lewej stronie i zmierzają w kierunku prawej liczby z przyrostem o jeden. Jeśli użyjesz większej liczby po lewej stronie, wynikiem będzie pusty zakres.
Jeśli chcesz iterować w przeciwnym kierunku, od większych do mniejszych liczb, użyj funkcji downTo
.
Domyślnym krokiem we wszystkich tych przypadkach jest 1
. Jeśli chcesz użyć innego kroku, powinieneś użyć funkcji infiksowej step
.
Break oraz continue
Wewnątrz pętli możemy użyć słów kluczowych break
i continue
:
break
- kończy najbliższą otaczającą pętlę.continue
- przechodzi do następnego kroku najbliższej otaczającej pętli.
Oba są stosowane raczej rzadko, a nawet miałem trudności ze znalezieniem choćby jednego realnego przykładu w większości komercyjnych projektów, które współtworzyłem. Zakładam również, że są dobrze znane programistom, którzy przyszli do Kotlina ze starszych języków. Dlatego tak krótko je przedstawiam.
Przypadki użycia
Programiści z doświadczeniem w starszych językach często iterują po liczbach tam, gdzie powinny być używane nieco bardziej nowoczesne alternatywy. Na przykład, w niektórych projektach można znaleźć pętlę for, która służy do iterowania po elementach z indeksami.
To nie jest dobre rozwiązanie. W Kotlinie istnieje wiele sposobów, aby zrobić to lepiej.
Po pierwsze, zamiast jawnie iterować po zakresie 0..<names.size
, moglibyśmy użyć właściwości indices
, która zwraca zakres dostępnych indeksów.
Po drugie, zamiast iterować po indeksach i znajdować element dla każdego z nich, moglibyśmy iterować po indeksowanych wartościach. Możemy tworzyć indeksowane wartości za pomocą withIndex
. Każda indeksowana wartość zawiera zarówno indeks, jak i wartość. Takie obiekty można destrukturyzować w pętli for2.
Po trzecie, jeszcze lepszym rozwiązaniem jest użycie forEachIndexed
, które jest wyjaśnione w kolejnej książce: Funkcyjny Kotlin.
Innym popularnym przypadkiem użycia jest iterowanie po mapie. Programiści z doświadczeniem w Javie często robią to w ten sposób:
Ten kod można poprawić, iterując bezpośrednio po mapie, więc wywołanie entries
jest zbędne. Dodatkowo możemy zdestrukturyzować wpisy, aby lepiej nazwać wartości.
Możemy również użyć forEach
dla mapy, co zostanie wyjaśnione w książce Funkcyjny Kotlin.
Podsumowanie
W tym rozdziale nauczyliśmy się korzystać z pętli for. W Kotlinie ta pętla jest naprawdę prosta i daje niezwykłe możliwości, więc warto wiedzieć, jak działa.
Teraz porozmawiajmy o jednym z najważniejszych ulepszeń, jakie Kotlin wprowadził do świata JVM: dobrym wsparciu dla obsługi wartości null
.
Ma metodę iterator
.
Destrukturyzacja będzie wyjaśniona bardziej szczegółowo w rozdziale Data klasy.