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 <= last override fun hasNext(): Boolean = hasNext override fun next(): Version { val value = next if (value == finalElement) { hasNext = false } else { next++ } return value } } } operator fun Version.inc(): Version { return copy(v + 1); }
Gotowe. By móc wykorzystać ++ musieliśmy jeszcze dodać przeciążenie tego operatora, ale to już tylko czynność ekstra, by uprościć zapis. Możemy teraz napisać:
Listing 7. Przykładowe użycie
fun main(args: Array<String>) { for (v in Version(1)..Version(5)) println(v) }
Podsumowanie
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.
PS
Iterator można zaimplementować z wykorzystaniem AbstractIterator:
Listing 8. Wykorzystanie AbstractIterator
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++) } } }
Czyni to kod bardziej zwięzłym.