阅读 2872

Kotlin中的Ranges以及自定义Range

什么是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]复制代码
文章分类
Android