Kotlin Koans 学习笔记 —— Unit 1
Kotlin Koans 学习笔记 —— Unit 2
25 Comparison
修改 MyDate.kt 实现 Comparable
接口
data class MyDate(val year: Int, val month: Int, val dayOfMonth: Int) :Comparable<MyDate>{
override fun compareTo(other: MyDate): Int = when {
year != other.year -> year - other.year
month != other.month -> month - other.month
else -> dayOfMonth - other.dayOfMonth
}
}
在 Kotlin 中继承、实现 都是使用 :
完成,当需要实现多个接口时,使用 ,
间隔。
26 InRange
在进行区间判断的时候,我们通常会使用 in
关键字来判断对象是否处于目标区间之内,实现该功能的关键就是实现 ClosedRange
接口,或者重载 contains
操作符
class DateRange(override val start: MyDate, override val endInclusive: MyDate):ClosedRange<MyDate>
class DateRange(val start: MyDate, val endInclusive: MyDate){
operator fun contains(item:MyDate):Boolean = start <= item && item <= endInclusive
}
请注意第一段代码,可以看到我们没有在代码重载 ClosedRange
中的任何方法,整个类也只是实现了这个接口而已,那么会什么会有等同于重载 contains
操作符的效果呢?只要看代码我们就知道怎么回事了:
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
}
可以注意到这个接口已经写好了 contains
操作符的默认实现了,注意我们代码中构造器class DateRange(override val start: MyDate, override val endInclusive: MyDate)
,我们override
了ClosedRange
接口中的两个参数,而在之前我们也实现了 MyDate
类的 Comparable
接口,所以我们无需再写其他代码了!
关于在Kotlin 中的操作符重载可以参考这篇博客:Kotlin-31.操作符/运算符重载(operator overload)
27 RangeTo
在判断区间时我们常常会用到这样的代码:
when(x){
in 0..10 -> {...}
in 11 until 100 ->{}
}
其中 ..
表示的就是一个闭合区间,这一点与 26 题类似,只不过一个用对象表示一个闭合区间,一个使用
..
运算符来表示一个闭合区间。这就需要我们重载 ..
运算符,该运算符对应的函数为 rangeTo(t:T)
返回值是一个闭合区间 ClosedRange。
//写在MyDate类的内部
operator fun rangeTo(other: MyDate): DateRange = DateRange(this,other)
//以扩展函数的形式写在外部
operator fun MyDate.rangeTo(other: MyDate): DateRange = DateRange(this,other)
28 ForLoop
in
关键字除了我们上面说过的用于判断某个对象是否处于一个区间之类,还在一个场合下经常使用,那就是在 for 循环中,比如我们最常见的:
val list = mutableListOf<String>("1","2","3")
for (i in list) {
println(i)
}
要向实现这种效果,我们就不只是需要我们的区间类实现 ClosedRange
这一个接口了,还需要实现 Iterable
接口。
老规矩,先来看一下 Iterable
这个接口:
public interface Iterable<out T> {
/**
* Returns an iterator over the elements of this object.
*/
public operator fun iterator(): Iterator<T>
}
这个接口中只包含一个操作符 iterator()
,它的返回值是 Iterator
,它包含了两个方法next()
、hasNext()
:
public interface Iterator<out T> {
/**
* Returns the next element in the iteration.
*/
public operator fun next(): T
/**
* Returns `true` if the iteration has more elements.
*/
public operator fun hasNext(): Boolean
}
修改 DateRange 类:
class DateRange(override val start: MyDate, override val endInclusive: MyDate) : ClosedRange<MyDate>, Iterable<MyDate> {
override fun iterator(): Iterator<MyDate> = object : Iterator<MyDate> {
var current: MyDate = start //这个start就是 .. 操作符的第一个参数
override fun next(): MyDate {
val result = current
current = current.nextDay()
return result
}
override fun hasNext(): Boolean = current <= endInclusive // 这是 .. 的第二个参数
}
}
for 循环从迭代器的next()
方法取得下一个数据,通过hasNext()
判断合适结束循环。
29 重载操作符
重载 +
:
operator fun MyDate.plus(timeInterval: TimeInterval) = addTimeIntervals(timeInterval,1)
重载 *
:
class RepeatedTimeInterval(val ti: TimeInterval, val n: Int=1)
//重载TimeInterval的 * 操作符,a.times(Int) = c
operator fun TimeInterval.times(number:Int) = RepeatedTimeInterval(this,number)
//重载MyDate的 + 操作符,用于满足MyDate + TimeInterval * Int , a+c
operator fun MyDate.plus(timeInterval: RepeatedTimeInterval) = addTimeIntervals(timeInterval.ti,timeInterval.n)
注意,重载乘法的时候我们还重载了一个加法运算,这是为了能满足以下表达式:
fun task29_2(today: MyDate): MyDate {
// todoTask29()
return today + YEAR * 2 + WEEK * 3 + DAY * 5
}
30 解构声明
class MyDate(val year: Int, val month: Int, val dayOfMonth: Int){
operator fun component1() = year
operator fun component2() = month
operator fun component3() = dayOfMonth
}
这四个字对于 Android + Java 开发者是挺陌生的,大家可以点击到 Kotlin 中文站查看详细的定义,下面我们通过代码来了解一下他的用法,首先我们来看一张熟悉的图:
注意这里的 (k,v)
这就是一个解构声明,标准库已经帮我们实现了解构声明,另外数据类也已经实现了主构造函数包含参数的解构声明。我们在使用componentN()
来定义结构声明时,N 填写的序号就是在使用解构声明时参数的顺序,N必须是顺序向下递增的,不可以随意填写!否则在使用时会有如下错误提示:
所以我们还可以这样解答这道题:
data class MyDate(val year: Int, val month: Int, val dayOfMonth: Int)
需要注意的是,由于数据类默认为我们实现了主构造函数的参数解构声明,所以我们的解构声明只能接着主构造函数的序号往后使用!
如果我们只是想获得解构声明中某个单独的参数,或者某几个参数我们还可以有以下的几种写法:
val date = MyDate(2018, 1, 23)
val (_, _, day) = date
println(day)
val month = date.component3()
println(month)
在 Kotlin 中 _
是占位符,对于我们不关心的参数,可以使用 _
占位而不用去费心思想参数名了,这一写法可以使用在解构与 Lambda 表达式参数上!
当我们队一个类进行了解构声明时,在 Lambda 表达式中我们可以直接使用其解构作为参数,如下:
date.let { (year, month, dayOfMonth) ->
println("year = $year , month = $month , dayOfMonth = $dayOfMonth")
}
请注意解构声明对于 Lambda 表达式而言是一个参数:
{ a //-> …… } // 一个参数
{ a, b //-> …… } // 两个参数
{ (a, b) //-> …… } // 一个解构对
{ (a, b), c //-> …… } // 一个解构对以及其他参数
31 Invoke 调用
也许你像我一样从来没有考虑过 method()
中 ()
到底意味着什么,它太自然了,自然到我们完全不会去考虑它,但是直到我在使用函数B 作为函数A 的参数传入时才发现了一些不一样的事情!
fun methodB(methodA: () -> Unit) {
//我们在函数B中调用函数A
methodA
}
在上面的代码中,我们声明了一个参数 methodA
,这是个参数是一个函数,我们期望在B的函数体中执行A,如果像我上面那样书写,编辑器不会报错,但是实际代码执行的时候不会调用A函数,原因很简单,就是我们没有加上 ()
操作符,此时 methodA 只是一个普通的对象,只不过他是一个函数对象而已!也就是说 ()
这个符号对于一个函数而言,起到的作用就是调用!在 Kotlin 中它同 +
、-
、=
等等一样,都是一个操作符!
正因为 methodA 只是一个对象,所以我们甚至可以这样:
fun methodB(methodA: () -> Unit) {
//我们在函数B中调用函数A
println(methodA.toString())
methodA.apply {
println("执行前先做点别的!")
invoke()
}
}
既然我们知道 ()
是一个操作符,不知道你有没有联想到前面我们曾经做过的 操作符重载 这道题,类似的,我们同样可以重载 ()
。
这道题中要求我们可以多次使用 ()
,所以我们需要让这个操作符的返回值还是原来的类!
class Invokable{
var numberOfInvocations: Int = 0
private set //set方法私有,防止外部调用
operator fun invoke():Invokable {
numberOfInvocations++
return this
}
}
额外说一点,重载 ()
操作符时,也是可以传入参数的!