Kotlin Koans学习笔记(3)

这是Kotlin Koans学习笔记的第三篇。
第一篇在这里第二篇在这里

这一部分一共7个任务,所有的任务都是围绕日期展开,日期对象具有年、月、日三个属性:

data class MyDate(val year: Int, val month: Int, val dayOfMonth: Int)

这一部分的任务主要是练习Kotlin对象运算符的重载。

Kotlin有固定数目的符号运算符,我们可以很简单的将这些运算符应用到任何一个类。具体的实现方法就是:对需要实现的运算符以Kotlin保留的函数名创建一个函数,这个运算符就会自动映射到相应的函数。为了提醒编译器我们重载了一个运算符,我们需要将重载函数标记为operator。 在后面的任务中我们可以看到这一特性将大大的提高代码的简洁性和可读性。

这里先列出全部的运算符和它对应的函数。某一个类如果要使用某一个运算符,就需要实现相应的函数。

单目运算符

表达式 对应的函数
+a a.unaryPlus()
-a a.unaryMinus()
!a a.not()
a++ a.inc()
a-- a.dec()

双目运算符

表达式 对应的函数
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)

类数组(Array-like)运算符

表达式 对应函数
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)

相等运算符

表达式 对应的函数
a==b a?.equals(b) ?:b === null
a==b !(a?.equals(b) ?:b === null)

函数调用

表达式 对应的函数
a(i) a.invoke(i)
a(i,j) a.invoke(i,j)
a(i_1, ..., i_n) a.invoke(i_1, ..., i_n)

这一部分的任务主要就是实现上面的这些运算符函数。

25.Comparsion

第一个任务要求实现日期对象大小的比较,如下代码能够返回比较结果:

fun task25(date1: MyDate, date2: MyDate): Boolean {
    //todoTask25()
    return date1 < date2
}

代码不做任何修改是编译不过的,编译器会提示MyDate类没有实现相关的函数。任务的提示也很清楚,MyDate类实现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
        }
}

通过实现Comparable接口,就可以在代码中直接使用大小比较运算符。

26.InRange

这个任务的要求编码实现检查指定的日期是不是在某一个日期范围内:

fun checkInRange(date: MyDate, first: MyDate, last: MyDate): Boolean {
    //todoTask26_()
    return date in DateRange(first, last)
}

根据前面的表格知道:a in b翻译以后就是b.contains(a),所以这里的任务就转换成实现DateRange类的contains(d: MyDate):Boolean函数。DateRange类的定义已经给出来了,它包含一个起始日期(start)和一个截止日期(endInclusive)。由于上一个任务已经实现了日期大小的比较,所以这个任务也就很好完成:

operator fun contains(d: MyDate) = (d>=start && d<=endInclusive)

注意函数定义前面的operator修饰符。

27.RangeTo

这一个任务是要求实现MyDate类的..运算符。..运算符最终会翻译成rangeTo()函数,所以本任务就是实现MyDate.rangeTo(),由于在上一个任务中DateRange已经实现了operator fun contains(d: MyDate)。所以rangeTo()函数返回一个DateRange对象即可:

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

任务要求可以对DateRange内的MyDate执行for循环,也就是要求DataRange实现Iterable<MyDate>接口。任务描述中也给出了足够的提示。日期范围的迭代以为迭代间隔,所以需要使用已经定义好的nextDay()方法,一次递增一天:

fun MyDate.nextDay() = addTimeIntervals(DAY, 1)

Iterator<MyDate>接口有两个方法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

这一个任务包含两个任务,主要练习重载运算符。第一个小任务是重载MyDate+运算符:

fun task29_1(today: MyDate): MyDate {
   // todoTask29()
    return today + YEAR + WEEK

MyDate可以和一个时间间隔相加,所以需要实现MyDate.plus()函数,以时间间隔为参数,有了上一个任务计算下一天的基础,实现和时间间隔相加就比较容易了:

operator fun MyDate.plus(timeInterval: TimeInterval) = addTimeIntervals(timeInterval, 1)

第二个小任务在第一个小任务的基础上更进一步,不再是加一个单一的间隔,要求相加多个时间间隔,如加3年,加7个星期。。。所以需要先实现时间间隔的*运算符,将TimeInterval的乘法结果定义成RepeatedTimeInterval:

class RepeatedTimeInterval(val timeInterval: TimeInterval, val number: Int)
operator fun TimeInterval.times(number: Int) = RepeatedTimeInterval(this, number)

由于这里*运算返回的是一个RepeatedTimeInterval对象,所以还需要实现MyDateRepeatedTimeInterval相加:

operator fun MyDate.plus(timeIntervals: RepeatedTimeInterval) = addTimeIntervals(timeIntervals.timeInterval, timeIntervals.number)

仍然是使用已有的addTimeIntervals方法, 将RepeatedTimeInterval对象的两个属性作为该方法的两个参数。这样,就可以实现以下这样的表达式运算:

fun task29_2(today: MyDate): MyDate {
    //todoTask29()
    return today + YEAR * 2 + WEEK * 3 + DAY * 5
}

30.Destructuring Declaration

Kotlin可以将一个对象的所有属性一次赋值给一堆变量:

val (name, age) = person 

这段代码会被编译成:

val name = person.component1()
val age = person.component2()

这就是为什么DataClass会自动生成componentN()函数。

任务的要求是判断一个日期是否是闰年,第一步需要将时间的属性赋值给年、月、日。这就需要使用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

终于到了这一部分的最后一个任务,invoke。Invoke是什么呢?如果一个类实现了invoke()这个函数,那么该类的实体对象在调用这个函数时可以省略函数名,当然invoke函数必须要有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)

执行结果就是:

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

回到我们的任务,要求如下代码返回的结果为4:

fun task31(invokable: Invokable): Int {
    //todoTask31()
    return invokable()()()().getNumberOfInvocations()
}

所以需要实现Invokableinvoke方法,该方法每调用一次,内部计数器就需要加1,最后返回计数器的值:

class Invokable {
    var numberOfInvocations: Int = 0
    operator fun invoke(): Invokable {
        numberOfInvocations++
        return this
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容