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
}
}
額外說一點,重載 ()
操作符時,也是可以傳入參數的!