[关闭]
@zhongzilu 2019-02-11T15:56:54.000000Z 字数 2746 阅读 3731

Kotlin 中的区间

我们最早接触到 “区间” 一词, 应该是数学老师告诉我们的数学意义上的实数范围区间, 它指的是, 从一个实数到另一个实数之间连续的实数集合, 分为 全开区间左开右闭区间左闭右开区间全闭区间 四种. 可以用符号分别表示为 (a, b)(a, b][a, b)[a, b]. 而今天我们要讲的 Kotlin 中的区间, 也是基于这个概念.

虽然数学上的区间分为 全开区间左开右闭区间左闭右开区间全闭区间 四种, 但 Kotlin 中的区间却只支持最后两种, 即 左闭右开区间全闭区间 , 因为所有区间都可以用这两种区间来表示, 比如:

区间 示例 闭区间表示 左闭右开表示
开区间 (1, 10) [2, 9] [2, 10)
闭区间 [1, 10] [1, 10] [1, 11)
左开右闭区间 (1, 10] [2, 10] [2, 11)
左闭右开区间 [1, 10) [1, 9] [1, 10)

本篇文章重点讲解以下几个方面, 让大家对 Kotlin 中的区间, 以及如何运用有所了解.

  • 它是什么?
  • 它的优点是什么?
  • 它的原理是什么?
  • 如何使用?

它是什么?

Kotlin 中的区间, 具体指的是 ClosedRange 接口, 以及它的实现类 IntRangeLongRangeCharRange. 你可以通过这三个类的构造方法来创建一个区间, 构造方法如下:

  1. IntRange(start: Int, endInclusive: Int)
  2. LongRange(start: Long, endInclusive: Long)
  3. CharRange(start: Char, endInclusive: Char)

但本文的重点不是通过构造方法创建区间, 而是另外一种方式, Kotlin 中有一种更简单的方式创建区间, 那就是通过区间操作符 .. 来创建, 比如创建一个包含 110 的闭区间, 相当于数学里的表达式 [1, 10] , 代码如下:

  1. val range = 1..10

是不是很简单? 该区间操作符在操作符重载相关文章中有提到. 使用该操作符最终会被编译为调用它对应的方法函数 rangeTo().

具体用法请看本文的 [ 如何使用 ] 章节的内容.

它的优点是什么?

它的优点就是可以通过简单的符号来创建一个区间, 而不需要通过构造函数那样繁琐的方式. 另外, 区间操作符配合 in 操作符, 可以实现区间的迭代, 也可以用来作为判断条件.

它的原理是什么?

使用区间操作符 .. 来创建区间, 会在编译期间编译成调用相应的 rangeTo() 方法函数, 返回的实例就是 IntRangeLongRangeCharRange 三个中一个, 比如 1..10 这样的表达式就是返回的 IntRange 类型的实例, 而 1L..10L 这样的表达式则返回的是 LongRange类型的实例.

简单的来说, 它最终还是通过构造方法的方式来创建的区间, 区间操作符来简化了我们自己实例化的过程.

如何使用?

最基本的使用, 比如用来判断某个数值 i 是否在 [1, 10] 内 , 用 Java 的方式大概如下:

  1. if (i >= 1 && i <= 10) System.out.println(i);

如果换做 Kotlin 的区间表示的话, 代码大概如下:

  1. if (i in 1..10) println(i)

直接判断数值 i 是否在区间内, 这是很直观的思维逻辑. 这里使用了 in 操作符, 之所以能用 in 操作符, 那是因为区间的共同接口 CloseRange<T> 重载了 in 操作符的对应方法函数 contains() :

  1. public operator fun contains(value: T): Boolean = value >= start && value <= endInclusive

而这里 1..10 表达式实际上创建的是 IntRange 实例, IntRange 就继承自该接口, 所以能使用 in 操作符.


整型区间 (IntRange、LongRange、CharRange) 有⼀个额外的特性:它们可以迭代。编译器会将其转换为类似 Java 的基于索引的 For 循环, 因此⽆额外的开销:

  1. for (i in 1..10) print(i) // 输出: "12345678910"

除了使用 For 循环的写法, 还可以使用 Kotlin 自带的 forEach 函数来对区间进行迭代, 写法如下:

  1. (1..10).forEach { print(it) } // 输出: "12345678910"

用括号 () 将区间表达式包裹住, 就可以使用 forEach 函数了. 当然, 只有整型区间才可以迭代.


如果你想创建一个不包含最后一项元素的区间, 例如 [1, 10), 那么可以使用 until 中缀操作符来实现, 如果不了解中缀操作符, 可以先去看看中缀操作符相关的文章. 代码如下:

  1. for (i in 1 until 10) print(i) // 输出: "123456789"

由于 Kotlin 中的区间, 都是左封闭区间, 如果你想创建一个不包含第一项和最后一项元素的区间, 即数学里的 全开区间, 那么就需要通过取巧的方式, 比如 (1, 10) 表示的区间, 代码可以如下:

  1. // 第一种方式: 转换成闭区间来表示
  2. for (i in 2..9) print(i) // 输出: "23456789"
  3. // 第二种方式: 转换成左闭右开区间来表示
  4. for (i in 2 until 10) print(i) // 输出: "23456789"

如果你想创建一个倒序的区间, 可以使用 downTo 中缀操作符, 也可以使用 reversed() 方法函数, 例如:

  1. // 第一种方式: 使用 downTo 中缀操作符
  2. for (i in 10 downTo 1) print(i) // 输出: "10987654321"
  3. // 第二种方式: 使用 reversed() 函数
  4. (1..10).reversed()

downTo 中缀操作符, 会包含区间的第一项和最后一项, 可以看作是一个 闭区间. 而 reversed() 函数操作的区间, 取决于它的调用者, 它是负责将区间进行反序而已.


如果你想在进行区间迭代的时候加上一个步进值, 那么可以使用 step 中缀操作符, 代码如下:

  1. for (i in 1..10 step 2) print(i) // 输出: "13579"

step 操作符是使用扩展函数的方式, 专为区间类型扩展的. 所以也只有区间类型才能使用.

总结

Kotlin 中的区间, 相当于数学概念中的闭区间, 只支持 左闭右开区间全闭区间 , 因为所有的区间都能用这两种区间来表示;

创建一个区间, 可以通过 IntRangeLongRangeCharRange 三者的构造函数的方式来创建, 也可以通过使用区间操作符 .. 来创建, 区间操作符 .. 创建的是一个 全闭区间, 而使用中缀 until 可以用来创建一个 左闭右开区间.

使用区间操作符的优点就是可以很简单的创建一个区间, 并且它可以应用到 For 循环迭代和 if 条件判断中.

For 循环迭代中, 可以通过使用中缀 downTo 或者调用 reversed() 方法函数来创建倒序的区间, 实现区间的倒序输出. 也可以使用中缀 step 来设置迭代的步进值.

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注