這是Kotlin Koans學(xué)習(xí)筆記的第三篇。
第一篇在這里址晕,第二篇在這里。
這一部分一共7個(gè)任務(wù)顿锰,所有的任務(wù)都是圍繞日期展開(kāi)斩箫,日期對(duì)象具有年吏砂、月、日三個(gè)屬性:
data class MyDate(val year: Int, val month: Int, val dayOfMonth: Int)
這一部分的任務(wù)主要是練習(xí)Kotlin對(duì)象運(yùn)算符的重載乘客。
Kotlin有固定數(shù)目的符號(hào)運(yùn)算符狐血,我們可以很簡(jiǎn)單的將這些運(yùn)算符應(yīng)用到任何一個(gè)類。具體的實(shí)現(xiàn)方法就是:對(duì)需要實(shí)現(xiàn)的運(yùn)算符以Kotlin保留的函數(shù)名創(chuàng)建一個(gè)函數(shù)易核,這個(gè)運(yùn)算符就會(huì)自動(dòng)映射到相應(yīng)的函數(shù)匈织。為了提醒編譯器我們重載了一個(gè)運(yùn)算符,我們需要將重載函數(shù)標(biāo)記為operator
牡直。 在后面的任務(wù)中我們可以看到這一特性將大大的提高代碼的簡(jiǎn)潔性和可讀性缀匕。
這里先列出全部的運(yùn)算符和它對(duì)應(yīng)的函數(shù)。某一個(gè)類如果要使用某一個(gè)運(yùn)算符碰逸,就需要實(shí)現(xiàn)相應(yīng)的函數(shù)乡小。
單目運(yùn)算符
表達(dá)式 | 對(duì)應(yīng)的函數(shù) |
---|---|
+a | a.unaryPlus() |
-a | a.unaryMinus() |
!a | a.not() |
a++ | a.inc() |
a-- | a.dec() |
雙目運(yùn)算符
表達(dá)式 | 對(duì)應(yīng)的函數(shù) |
---|---|
a+b | a.plus(b) |
a-b | a.minus(b) |
a*b | a.times(b) |
a/b | a.div(b) |
a%b | a.mod(b) |
a..b | a.rangeTo(b) |
a in b | b.contains(a) |
a !in b | !b.contains(a) |
a+=b | a.plusAssign(b) |
a-=b | a.minusAssign(b) |
a*=b | a.timesAssign(b) |
a/=b | a.divAssign(b) |
a%=b | a.modAssign(b) |
類數(shù)組(Array-like)運(yùn)算符
表達(dá)式 | 對(duì)應(yīng)函數(shù) |
---|---|
a[i] | a.get(i) |
a[i,j] | a.get(i,j) |
a[i_1, ..., i_n] | a.get(i_1, ..., i_n) |
a[i]=b | a.set(i,b) |
a[i,j]=b | a.set(i,j,b) |
a[i_1, ..., i_n]=b | a.set(i_1, ..., i_n, b) |
相等運(yùn)算符
表達(dá)式 | 對(duì)應(yīng)的函數(shù) |
---|---|
a==b | a?.equals(b) ?:b === null |
a==b | !(a?.equals(b) ?:b === null) |
函數(shù)調(diào)用
表達(dá)式 | 對(duì)應(yīng)的函數(shù) |
---|---|
a(i) | a.invoke(i) |
a(i,j) | a.invoke(i,j) |
a(i_1, ..., i_n) | a.invoke(i_1, ..., i_n) |
這一部分的任務(wù)主要就是實(shí)現(xiàn)上面的這些運(yùn)算符函數(shù)。
25.Comparsion
第一個(gè)任務(wù)要求實(shí)現(xiàn)日期對(duì)象大小的比較饵史,如下代碼能夠返回比較結(jié)果:
fun task25(date1: MyDate, date2: MyDate): Boolean {
//todoTask25()
return date1 < date2
}
代碼不做任何修改是編譯不過(guò)的满钟,編譯器會(huì)提示MyDate
類沒(méi)有實(shí)現(xiàn)相關(guān)的函數(shù)。任務(wù)的提示也很清楚胳喷,MyDate
類實(shí)現(xiàn)Comparable
接口即可:
data class MyDate(val year: Int, val month: Int, val dayOfMonth: Int): Comparable<MyDate> {
override fun compareTo(other: MyDate) =
when{
other.year != year -> year - other.year
other.month != month -> month - other.month
else -> dayOfMonth - other.dayOfMonth
}
}
通過(guò)實(shí)現(xiàn)Comparable接口湃番,就可以在代碼中直接使用大小比較運(yùn)算符。
26.InRange
這個(gè)任務(wù)的要求編碼實(shí)現(xiàn)檢查指定的日期是不是在某一個(gè)日期范圍內(nèi):
fun checkInRange(date: MyDate, first: MyDate, last: MyDate): Boolean {
//todoTask26_()
return date in DateRange(first, last)
}
根據(jù)前面的表格知道:a in b
翻譯以后就是b.contains(a)
,所以這里的任務(wù)就轉(zhuǎn)換成實(shí)現(xiàn)DateRange
類的contains(d: MyDate):Boolean
函數(shù)吭露。DateRange類的定義已經(jīng)給出來(lái)了吠撮,它包含一個(gè)起始日期(start
)和一個(gè)截止日期(endInclusive
)。由于上一個(gè)任務(wù)已經(jīng)實(shí)現(xiàn)了日期大小的比較讲竿,所以這個(gè)任務(wù)也就很好完成:
operator fun contains(d: MyDate) = (d>=start && d<=endInclusive)
注意函數(shù)定義前面的operator
修飾符泥兰。
27.RangeTo
這一個(gè)任務(wù)是要求實(shí)現(xiàn)MyDate
類的..
運(yùn)算符。..
運(yùn)算符最終會(huì)翻譯成rangeTo()
函數(shù)题禀,所以本任務(wù)就是實(shí)現(xiàn)MyDate.rangeTo()
逾条,由于在上一個(gè)任務(wù)中DateRange
已經(jīng)實(shí)現(xiàn)了operator fun contains(d: MyDate)
。所以rangeTo()
函數(shù)返回一個(gè)DateRange
對(duì)象即可:
operator fun MyDate.rangeTo(other: MyDate): DateRange = DateRange(this, other)
fun checkInRange2(date: MyDate, first: MyDate, last: MyDate): Boolean {
//todoTask27()
return date in first..last
}
28.ForLoop
任務(wù)要求可以對(duì)DateRange內(nèi)的MyDate執(zhí)行for循環(huán)投剥,也就是要求DataRange
實(shí)現(xiàn)Iterable<MyDate>
接口师脂。任務(wù)描述中也給出了足夠的提示。日期范圍的迭代以天
為迭代間隔江锨,所以需要使用已經(jīng)定義好的nextDay()
方法吃警,一次遞增一天:
fun MyDate.nextDay() = addTimeIntervals(DAY, 1)
Iterator<MyDate>
接口有兩個(gè)方法next()
和hasNext()
:
lass DateRange(val start: MyDate, val endInclusive: MyDate) : Iterable<MyDate>{
...
override fun iterator(): Iterator<MyDate> = DateIterator(this)
}
class DateIterator(val dateRange:DateRange) : Iterator<MyDate> {
var current: MyDate = dateRange.start
override fun next(): MyDate {
val result = current
current = current.nextDay()
return result
}
override fun hasNext(): Boolean = current <= dateRange.endInclusive
}
29.OperatorOverloading
這一個(gè)任務(wù)包含兩個(gè)任務(wù),主要練習(xí)重載運(yùn)算符啄育。第一個(gè)小任務(wù)是重載MyDate
的+
運(yùn)算符:
fun task29_1(today: MyDate): MyDate {
// todoTask29()
return today + YEAR + WEEK
MyDate
可以和一個(gè)時(shí)間間隔相加酌心,所以需要實(shí)現(xiàn)MyDate.plus()
函數(shù),以時(shí)間間隔為參數(shù)挑豌,有了上一個(gè)任務(wù)計(jì)算下一天的基礎(chǔ)安券,實(shí)現(xiàn)和時(shí)間間隔相加就比較容易了:
operator fun MyDate.plus(timeInterval: TimeInterval) = addTimeIntervals(timeInterval, 1)
第二個(gè)小任務(wù)在第一個(gè)小任務(wù)的基礎(chǔ)上更進(jìn)一步墩崩,不再是加一個(gè)單一的間隔,要求相加多個(gè)時(shí)間間隔侯勉,如加3年鹦筹,加7個(gè)星期。址貌。铐拐。所以需要先實(shí)現(xiàn)時(shí)間間隔的*
運(yùn)算符,將TimeInterval
的乘法結(jié)果定義成RepeatedTimeInterval
:
class RepeatedTimeInterval(val timeInterval: TimeInterval, val number: Int)
operator fun TimeInterval.times(number: Int) = RepeatedTimeInterval(this, number)
由于這里*
運(yùn)算返回的是一個(gè)RepeatedTimeInterval
對(duì)象练对,所以還需要實(shí)現(xiàn)MyDate
和RepeatedTimeInterval
相加:
operator fun MyDate.plus(timeIntervals: RepeatedTimeInterval) = addTimeIntervals(timeIntervals.timeInterval, timeIntervals.number)
仍然是使用已有的addTimeIntervals
方法遍蟋, 將RepeatedTimeInterval
對(duì)象的兩個(gè)屬性作為該方法的兩個(gè)參數(shù)。這樣螟凭,就可以實(shí)現(xiàn)以下這樣的表達(dá)式運(yùn)算:
fun task29_2(today: MyDate): MyDate {
//todoTask29()
return today + YEAR * 2 + WEEK * 3 + DAY * 5
}
30.Destructuring Declaration
Kotlin可以將一個(gè)對(duì)象的所有屬性一次賦值給一堆變量:
val (name, age) = person
這段代碼會(huì)被編譯成:
val name = person.component1()
val age = person.component2()
這就是為什么DataClass
會(huì)自動(dòng)生成componentN()
函數(shù)虚青。
任務(wù)的要求是判斷一個(gè)日期是否是閏年,第一步需要將時(shí)間的屬性賦值給年螺男、月棒厘、日。這就需要使用destructuring declaration
烟号。
<pre>
data class MyDate(val year: Int, val month: Int, val dayOfMonth: Int)
fun isLeapDay(date: MyDate): Boolean {
//todoTask30()
val (year, month, dayOfMonth) = date
// 29 February of a leap year
return year % 4 == 0 && month == 2 && dayOfMonth == 29
}
</pre>
注意如果需要使用Destructuring Declaration,類必須聲明為data
類型政恍。
31.Invoke
終于到了這一部分的最后一個(gè)任務(wù)汪拥,invoke。Invoke是什么呢篙耗?如果一個(gè)類實(shí)現(xiàn)了invoke()
這個(gè)函數(shù)迫筑,那么該類的實(shí)體對(duì)象在調(diào)用這個(gè)函數(shù)時(shí)可以省略函數(shù)名,當(dāng)然invoke
函數(shù)必須要有operator
修飾符宗弯。先看一下栗子吧:
class customClass{
operator fun invoke(param: Int){
println("print in invoke method, param is :"+param)
}
operator fun invoke(param: Int, msg: String): customClass{
println("print in invoke method, param is :"+param+",message is:" + msg)
return this
}
}
那么就可以這樣使用:
val printer = customClass()
printer(2)
printer.invoke(4)
printer(12, "first")(23, "second")(46)
執(zhí)行結(jié)果就是:
print in invoke method, param is :2
print in invoke method, param is :4
print in invoke method, param is :12,message is:first
print in invoke method, param is :23,message is:second
print in invoke method, param is :46
回到我們的任務(wù)脯燃,要求如下代碼返回的結(jié)果為4:
fun task31(invokable: Invokable): Int {
//todoTask31()
return invokable()()()().getNumberOfInvocations()
}
所以需要實(shí)現(xiàn)Invokable
的invoke
方法,該方法每調(diào)用一次蒙保,內(nèi)部計(jì)數(shù)器就需要加1辕棚,最后返回計(jì)數(shù)器的值:
class Invokable {
var numberOfInvocations: Int = 0
operator fun invoke(): Invokable {
numberOfInvocations++
return this
}
}