Zakresy i iteracja
Kolejny mały temat, zanim powrócimy do funkcji. Jak wiemy, Kotlin pozwala na przeciążanie operatorów. Jednym z nich jest operator podwójnej kropki – .., który służy do stworzenia zakresu. A do czego może posłużyć nam zakres? Na przykład do uproszczenia zapisu w pętli:
Listing 1. Wykorzystanie zakresu Int
fun main(args: Array<String>) {
for (i in 1..5)
println(i)
}
Własne zakresy
Oczywiście możemy utworzyć własny zakres. W tym celu należy przeciążyć operator .. oraz utworzyć klasę rozszerzającą ClosedRange. Dodatkowo klasa, która będzie wyposażona w zakres musi implementować Comparable.
Listing 2. Przygotowanie klasy Version do działania w zakresach
data class Version(val v: Int) : Comparable<Version> {
override fun compareTo(other: Version): Int {
return this.v.compareTo(other.v)
}
}
Kolejnym krokiem jest dodanie odpowiedniego operatora.
Listing 3. Dodanie operatora .. do klasy Version
operator fun Version.rangeTo(b: Version): VersionRange {
return VersionRange(this, b)
}
W tym miejscu wiemy już, że potrzebujemy klasy VersionRange, która będzie implementować ClosedRange.
Listing 4. Klasa VersionRange
class VersionRange(override val start: Version, override val endInclusive: Version) : ClosedRange<Version>
W ten sposób mamy już skonfigurowany nasz zakres. Jednak nadal nie możemy użyć go w pętli. Sam zakres opisuje tylko pewien zbiór wartości. Nie mówi nic o tym, jak należy się w nim poruszać. W tym celu należy zaimplementować interfejs Iterable:
Listing 5. Klasa VersionRange
class VersionRange(override val start: Version, override val endInclusive: Version) : ClosedRange<Version>, Iterable<Version>{
override fun iterator(): Iterator<Version> = VersionIterator(start, endInclusive)
}
Implementacja interfejsu pociąga za sobą konieczność stworzenia klasy VersionIterator:
Listing 6. Klasa VersionIterator
class VersionRange(override val start: Version, override val endInclusive: Version) : ClosedRange<Version>, Iterable<Version>{
override fun iterator(): Iterator<Version> = VersionIterator(start, endInclusive)
private class VersionIterator(first: Version, last: Version) : Iterator<Version> {
private var next = first
private val finalElement = last
private var hasNext: Boolean = first
<p>Gotowe. By móc wykorzystać <samp>++</samp> musieliśmy jeszcze dodać przeciążenie tego operatora, ale to już tylko czynność ekstra, by uprościć zapis. Możemy teraz napisać:</p>
<p class="listing">Listing 7. Przykładowe użycie</p>kotlin
fun main(args: Array<String>) {
for (v in Version(1)..Version(5))
println(v)
}
<h4>Podsumowanie</h4>
<p>Tworzenie własnych zakresów bywa przydatne. Szczególnie tam gdzie klasy domenowe mają „naturalną” właściwość do bycia zakresami. Wszelkiej maści wersje, numery seryjne, numery dokumentów są świetnymi kandydatami do tej operacji.</p>
<h4>PS</h4>
<p>Iterator można zaimplementować z wykorzystaniem <samp>AbstractIterator</samp>:</p>
<p class="listing">Listing 8. Wykorzystanie <samp>AbstractIterator</samp></p>kotlin
class VersionRange(override val start: Version, override val endInclusive: Version) : ClosedRange<Version>, Iterable<Version> {
override fun iterator(): Iterator<Version> = VersionIterator(start, endInclusive)
private class VersionIterator(var first: Version, val last: Version) : AbstractIterator<Version>() {
override fun computeNext() {
if(first > last)
return done();
setNext(first++)
}
}
}
<p>Czyni to kod bardziej zwięzłym. </p>