Kotlin中的Ranges以及自定義Range

飛潯發表於2019-03-04

什麼是Range

  • Range是Kotlin相對Java新增的一種表示式,它表示的是值的範圍,類似於數學中的區間。
  • Range的表示式是像這樣子的:1..20,其中..是運算子,它表示一個閉區間[1, 20]。而右開區間用until表示:1 until 20,即[1, 20)。
  • Range表示式一般是和in!in操作符一起使用,表示是否包含在該區間內,例如:
      if (i in 1..20){ //相當於 i >= 1 && i <= 20
          ...
      }複製程式碼
  • 對於一些整形的range(IntRangeLongRangeCharRange)是可以進行迭代的,它們可以和for迴圈一起使用,例如:
      for (i in 1..4) print(i) // 輸出 "1234"
      for (i in 4..1) print(i) // 因為"4..1"這個區間為空,所以什麼都沒有輸出複製程式碼

    Kotlin 1.1以後新增了DoubleFloat的range,但是它們只能進行in!in操作,不能對它們進行迭代。

  • 使用downTo()函式可以對range進行倒序迭代,例如
      for (i in 4 downTo 1) print(i) // 輸出 "4321"複製程式碼
  • 使用step()函式,可以修改每次迭代增加的值,例如:
      for (i in 1..4 step 2) print(i)  // 輸出 "13"
      for (i in 4 downTo 1 step 2) print(i) // 輸出 "42"複製程式碼

How it works

range是如何實現和工作的呢?我們知道1..20這個表示式是Int中實現了rangeTO()操作符,它等價於1.rangTo(20),返回一個IntRange(1, 20),Kotlin中的原始碼如下

class Int {
    //...
    operator fun rangeTo(other: Long): LongRange = LongRange(this, other)
    //...
    operator fun rangeTo(other: Int): IntRange = IntRange(this, other)
    //...
}複製程式碼

下面將以IntRange為例,簡單分析Range的實現和工作。下圖為IntRange的類圖:

IntRange類圖
IntRange類圖

IntRange實現了ClosedRange<T>介面,該介面需要傳入一個實現了Comparable<T>介面的範型,對於IntRange來說就是Int。
ClosedRange<T>就相當於上面說的閉區間,區間的兩個端點分別是介面中的兩個引數:startendInclusive,最主要的是它的contains()函式。startendInclusive必須要實現該介面的類去override,而contains()已經在ClosedRange中實現了,則不需要進行重寫。這也是Kotlin和Java不同的地方之一:介面中可以有方法實現,也可以只定義方法簽名;也可以有自己的屬性,但是不能對屬性進行初始化,必須由實現它的類進行初始化,否則抽象類就要下崗了。以下是該介面的原始碼:

public interface ClosedRange<T: Comparable<T>> {
    /**
     * The minimum value in the range.
     */
    public val start: T

    /**
     * The maximum value in the range (inclusive).
     */
    public val endInclusive: T

    /**
     * Checks whether the specified [value] belongs to the range.
     */
    public operator fun contains(value: T): Boolean = value >= start && value <= endInclusive

    /**
     * Checks whether the range is empty.
     */
    public fun isEmpty(): Boolean = start > endInclusive
}複製程式碼

IntRange中給兩個端點進行賦值,程式碼如下:

public class IntRange(start: Int, endInclusive: Int) : IntProgression(start, endInclusive, 1), ClosedRange<Int> {
    override val start: Int get() = first
    override val endInclusive: Int get() = last
    // ...
}複製程式碼

這個firstlast是什麼東西?點進去之後,發現是它的父類IntProgression中的值。

public open class IntProgression
    internal constructor
    (
            start: Int,
            endInclusive: Int,
            step: Int
    ) : Iterable<Int> {
    init {
        if (step == 0) throw kotlin.IllegalArgumentException("Step must be non-zero")
    }

    /**
     * The first element in the progression.
     */
    public val first: Int = start

    /**
     * The last element in the progression.
     */
    public val last: Int = getProgressionLastElement(start.toInt(), endInclusive.toInt(), step).toInt()

    /**
     * The step of the progression.
     */
    public val step: Int = step

    override fun iterator(): IntIterator = IntProgressionIterator(first, last, step)

    // ...

    companion object {
        public fun fromClosedRange(rangeStart: Int, rangeEnd: Int, step: Int): IntProgression = IntProgression(rangeStart, rangeEnd, step)
    }
}複製程式碼

IntProgression的作用主要有兩個:

  1. 確定迭代時區間中的最後一個值last,由於迭代時step可以不為1,這個值有可能不等於區間右邊的值。例如for(i in 1..20 step 3),最後一個值應該是19。該值通過progressionUtil中的函式計算得來。
  2. 迭代功能的真正實現。IntProgression實現了Iterable<T>,這個介面就是用來實現迭代功能。重寫介面的iterator()函式,返回一個迭代器,這個迭代器必須實現Iterator<T>IntPresssion返回一個IntProgressionIterator的迭代器,迭代需要的hasNext()next()真正實現就在這個類裡。

     internal class IntProgressionIterator(first: Int, last: Int, val step: Int) : IntIterator() {
         private val finalElement = last
         private var hasNext: Boolean = if (step > 0) first <= last else first >= last
         private var next = if (hasNext) first else finalElement
    
         override fun hasNext(): Boolean = hasNext
    
         override fun nextInt(): Int {
             val value = next
             if (value == finalElement) {
                 if (!hasNext) throw kotlin.NoSuchElementException()
                 hasNext = false
             }
             else {
                 next += step
             }
             return value
         }
     }複製程式碼
     public abstract class IntIterator : Iterator<Int> {
         override final fun next() = nextInt()
    
         /** Returns the next value in the sequence without boxing. */
         public abstract fun nextInt(): Int
     }複製程式碼
     public interface Iterator<out T> {
         public operator fun next(): T
         public operator fun hasNext(): Boolean
     }複製程式碼

    總結:IntRange實現的介面ClosedRange實現了區間功能,父類IntProgression實現了迭代功能。LongRangeCharRange原理和IntRange相同。我們只要實現了這個兩個介面,就可以定義自己range,並對它進行迭代操作。

自定義range

  1. 建立一個實現了Comparable<T>介面的類,這個類就是區間裡的元素,相當於前面介紹的Int。重寫compareTo操作符函式,重寫該方法之後,就可以使用><運算子對該類進行運算。這裡我們以一個MyDate的日期類為例:
     data class MyDate(val year: Int, val month: Int, val dayOfMonth: Int) : Comparable<MyDate> {
         override operator fun compareTo(date: MyDate): Int {
             if (year != date.year) {
                 return year - date.year
             } else if (month != date.month) {
                 return month - date.month
             } else {
                 return dayOfMonth - date.dayOfMonth
             }
         }
     }複製程式碼
     println(MyDate(2016, 11, 11) > MyDate(2017, 10, 10)) // 輸出 false複製程式碼
  2. 建立一個range類,實現ClosedRange<T>介面,因為使用Mydate進行比較,這裡的T需要傳入MyDate。給Mydate新增rangeTo操作符函式。

     class DateRange(override val endInclusive: MyDate, override val start: MyDate) :ClosedRange<MyDate>{
         // ...
     }複製程式碼
     operator fun MyDate.rangeTo(other:MyDate) = DateRange(this, other)複製程式碼

    這裡通過擴充套件函式的方式,為MyDate實現rangeTo操作符函式。返回一個DateRange。呼叫如下:

     val first = MyDate(2016, 11, 11)
     val second = MyDate(2017, 10, 10)
     val other = MyDate(2017, 1, 1)
    
     println(other in first..second) // 輸出 true複製程式碼
  3. DateRange實現Iterable<MyDate>的介面,重寫介面中的iterator()方法,返回一個Iterator<MyDate>物件,這裡我們返回一個實現Iterator<T>介面的DateInterator物件,該物件真正實現了迭代的hasNext()next()方法,實現迭代功能:
     class DateRange( override val start: MyDate, override val endInclusive: MyDate) : Iterable<MyDate>, ClosedRange<MyDate> {
         override fun iterator(): Iterator<MyDate> = DateIterator(start, endInclusive)
     }複製程式碼
     class DateIterator(first: MyDate, val last: MyDate) : Iterator<MyDate> {
         var hasNext = first <= last
         var next = if (hasNext) first else last
         override fun hasNext(): Boolean = hasNext
         override fun next(): MyDate {
             val result = next
             next = next.addOneDay()
             hasNext = next <= last
             return result
         }
     }複製程式碼
     fun MyDate.addOneDay():MyDate{
         val c = Calendar.getInstance()
         c.set(this.year, this.month, this.dayOfMonth)
         c.add(Calendar.DAY_OF_MONTH, 1)
         return MyDate(c.get(Calendar.YEAR), c.get(Calendar.MONTH), c.get(Calendar.DAY_OF_MONTH))
     }複製程式碼

其他的函式

downTo()

整形如IntLong中可以呼叫,如下:

fun Long.downTo(other: Int): LongProgression {
    return LongProgression.fromClosedRange(this, other.toLong(), -1L)
}複製程式碼
fun Byte.downTo(other: Int): IntProgression {
    return IntProgression.fromClosedRange(this.toInt(), other, -1)
}複製程式碼

作用就是迭代的時候,step為-1,例如:

for(i in 5.downTo(1)){
    print(i) //輸出 5 4 3 2 1
}複製程式碼

reversed()

*Progression類中的擴充套件方法,返回一個倒序的序列

fun IntProgression.reversed(): IntProgression {
    return IntProgression.fromClosedRange(last, first, -step)
}複製程式碼
for (i in (5..1).reversed()){
    print(i) //輸出1 2 3 4 5
}複製程式碼

step()

也是定義在*Progession類的擴充套件方法中,修改迭代的step,這個值必須為正數,所以該方法不會修改迭代的方向。

fun IntProgression.step(step: Int): IntProgression {
    if (step <= 0) throw IllegalArgumentException("Step must be positive, was: $step")
    return IntProgression.fromClosedRange(first, last, if (this.step > 0) step else -step)
}

fun CharProgression.step(step: Int): CharProgression {
    if (step <= 0) throw IllegalArgumentException("Step must be positive, was: $step")
    return CharProgression.fromClosedRange(first, last, if (this.step > 0) step else -step)
}複製程式碼

要注意的是,該方法返回的*Progression中的last值可能不是原來的last值。因為要保證(last - first) % step == 0。例如:

(1..12 step 2).last == 11  // progression with values [1, 3, 5, 7, 9, 11]
(1..12 step 3).last == 10  // progression with values [1, 4, 7, 10]
(1..12 step 4).last == 9   // progression with values [1, 5, 9]複製程式碼

相關文章