【用 Kotlin 寫 Android】Kotlin Koans 深入分析講解(上)

前言

具體 Kotlin 是什么?我相信你已經(jīng)在網(wǎng)絡(luò)上其他地方看到過解釋,這里不再贅述,簡單一句話:“Kotlin 是一種與 Java、C++ 平級的函數(shù)式編程語言”。在上一篇文章中,我們應(yīng)該看到了,Kotlin 有很多的基礎(chǔ)特殊的語法,讓代碼變得簡單清晰 (可能在開始的時候你反而覺得變得復(fù)雜了),但 Kotlin 真的就是語法的改變嗎?花兩個小時看看 Kotlin 的語法就算學(xué)完了 Kotlin 嗎?非也!Kotlin 與 Java 最大的區(qū)別是 Kotlin 的函數(shù)式編程,這一點以后我們會經(jīng)常提到,Kotlin 的核心與 Java 就有本質(zhì)的不同。Kotlin Koans 是學(xué)習(xí) Kotlin 極好的學(xué)習(xí)資料,我們先把 Kotlin Koans 做一遍,相信你會對 Kotlin 有完全不同的認識。

Kotlin Koans 項目詳解

Kotlin Koans 是一個 Kotlin 學(xué)習(xí)的教程,采用的方式是給你未完成的代碼,給你一點提示,你去補全代碼,運行單元測試,通過后進入下一題,可以非常好的學(xué)習(xí) Kotlin。

Kotlin Koans 的安裝,可以在 GitHub 上下載源代碼項目,但是我不建議這樣做,我建議的方式是安裝 EduTools 插件,安裝方式是直接在插件中心中搜索 EduTools 就可以了:

圖片

然后重啟 Android Studio??梢钥吹降谝豁?Browse Courses:

圖片

點擊選擇 Kotlin Koans 項目:

圖片

大約需要一分鐘后,會創(chuàng)建項目:

圖片

感覺提示,完成第一個任務(wù):

圖片

這個任務(wù)就是上一篇中函數(shù)的定義相關(guān)的內(nèi)容,我們可以看到得到如下一些信息:

  1. Kotlin 文件后綴為 .kt
  2. Kotlin 文件不需要定義類,可以直接定義方法
  3. 簡單的方法可以省略大括號,直接在 = 后寫返回值

每一個 Task 詳解

Task 2:Java to Kotlin conversion

圖片

將 Java 代碼轉(zhuǎn)換成 Kotlin 寫法。我們打開 JavaCode.java 文件,選中 toJSON 方法,復(fù)制,到 Task.kt 中粘貼(粘貼需要選中代碼),粘貼后可以發(fā)現(xiàn) Java 代碼自動轉(zhuǎn)換成了 Kotlin 代碼,很方便。

圖片
圖片

除了上面這種方法,還可以用 Android Studio 中代碼裝換工具,將 Java 文件轉(zhuǎn)換成 Kotlin:

圖片
圖片

我們在這里注意幾個問題:

  1. 函數(shù)的定義,用 fun 關(guān)鍵字
  2. 參數(shù)先寫參數(shù)名,再寫參數(shù)類型,中間用冒號 : 分割
  3. 方法參數(shù)后和大括號中間需要寫函數(shù)返回值類型,用 : 說明
  4. 創(chuàng)建對象沒有 new 關(guān)鍵字,直接類名后跟括號創(chuàng)建新對象
  5. 屬性定義用 val(不可更改) 或 var(可以更改)

Task 3:Named arguments

圖片

Kotlin 中參數(shù)是可以有默認值的,并且在調(diào)用的時候,可以顯示聲明哪些變量用哪些值,而不一定必須要按順序賦值,其他的值使用默認值,很方便靈活,這樣就可以減少構(gòu)造函數(shù)、重載函數(shù)的數(shù)量。Java 中兩個重載函數(shù)參數(shù)的類型如果都一樣的話,是不允許的,但是 Kotlin 使用默認值的方式就可以很好的避免這個問題(聲明一個三個參數(shù)的方法,并加上默認值,調(diào)用的時候?qū)Σ煌膮?shù)進行聲明賦值,就可以達到 Java 需要多個不同類型從在才能達到的目的)。

/**
 * Creates a string from all the elements separated using [separator] and using the given [prefix] and [postfix] if supplied.
 *
 * If the collection could be huge, you can specify a non-negative value of [limit], in which case only the first [limit]
 * elements will be appended, followed by the [truncated] string (which defaults to "...").
 */
public fun <T> Iterable<T>.joinToString(separator: CharSequence = ", ", prefix: CharSequence = "", postfix: CharSequence = "", limit: Int = -1, truncated: CharSequence = "...", transform: ((T) -> CharSequence)? = null): String {
    return joinTo(StringBuilder(), separator, prefix, postfix, limit, truncated, transform).toString()
}
圖片

Task 4:Default arguments

圖片

看題目可以看出,有些方法在調(diào)用的時候報錯,提示有些值沒有別賦值,那我們的任務(wù)就是在函數(shù)定義的時候加上合適的默認值,分析提示給出的 Java 代碼,我們可以看到,最后一個如果 number 沒有被賦值,會用默認值 42 調(diào)用第二個方法,而第二個方法 toUpperCase 沒有被賦值,使用默認值 false 調(diào)用第一個方法,則可以寫出答案:

圖片
圖片

在這里我們可以看到合理的使用默認值,可以極大的簡化代碼。

Task 5:Lambdas

圖片

找偶數(shù)很簡單,每一個都分別除 2 看余數(shù)是否為 0,但是剛剛看到這道問題可能會摸不著頭腦,怎么寫,這什么意思呀?題目名稱叫 Lambdas,Java 8 中支持 Lambda 表達式,Kotlin 也同樣支持,怎么寫呢。如果想解決這個問題,我們最好解決幾個概念:

  • 函數(shù)在 Kotlin 中是一等公民(First-class function)
    • 函數(shù)可以像變量一樣傳遞給其他函數(shù)作為參數(shù)(某一個函數(shù)有參數(shù),這個參數(shù)是一個變量)
    • 函數(shù)可以作為其他函數(shù)的返回值
    • 函數(shù)可以用于給變量賦值可以存儲在數(shù)據(jù)結(jié)構(gòu)中
  • 高階函數(shù)(Higher-Order Functions)
    • 函數(shù)作為函數(shù)的參數(shù)
    • 函數(shù)作為函數(shù)的返回值
  • 函數(shù)式編程(Functional programming)
    • 函數(shù)式編程是和面向?qū)ο蟆⒚嫦蜻^程同一級別的編程方法或編程模式
    • 函數(shù)是一等公民是函數(shù)式編程的必要條件,經(jīng)常用在高階函數(shù)中
    • 后面我們會有單獨的文章介紹函數(shù)式編程
圖片

我們可以看到 any 函數(shù)的定義,predicate 參數(shù)是一個函數(shù),這個函數(shù)的返回值是一個 Boolean 類型的值:

圖片
圖片
圖片
圖片

簡化到最后,就很簡單了。有沒有覺得函數(shù)式編程特別神奇呢?

Task 6:Strings

圖片

Kotlin 字符串可以像 Java 一樣,也可以用三個雙引號聲明,三個雙引號聲明的字符串其中可以包含多行。字符串模板可以很方便在字符串中使用變量,則這個題的答案就是:

圖片

這里我們用了 trimIndent() 方法對縮進進行格式化,還可以使用 trimMargin(),如下圖,其中的豎線 | 是trimMaring 默認格式化特殊字符:

圖片

Task 7:Data classes

圖片

Date Class 是什么東西?我們先來了解 Kotlin 中集中數(shù)據(jù)結(jié)構(gòu):

  • Class
    • 與 Java 類類似,是一種數(shù)據(jù)結(jié)構(gòu)
    • Kotlin 中的 Class 繼承自 Any,而不是 Object
    • 用關(guān)鍵字 constructor 聲明構(gòu)造函數(shù),夠著函數(shù)可以有默認值
    • 構(gòu)造函數(shù)分為主構(gòu)造函數(shù)和二級構(gòu)造函數(shù),主構(gòu)造函數(shù)跟在類名后由關(guān)鍵字 constructor 和參數(shù)表共同構(gòu)成,二級構(gòu)造函數(shù)在類中,用 constructor 和參數(shù)列表構(gòu)成,注意如果有主構(gòu)造函數(shù),二級函數(shù)必須調(diào)用主構(gòu)造函數(shù)。如 constructor(name: String, parent: Person) : this(name)。
    • 主構(gòu)造函數(shù)無法執(zhí)行代碼,如果需要執(zhí)行一些代碼時,可以用 init{} 在類中執(zhí)行,可以聲明多個 init{},執(zhí)行順序與聲明順序一致。
    • 默認類不可以被集成,方法不可以被重寫,如果希望類被繼承或方法可以被重寫,可以在類或方法前加 open 關(guān)鍵字。
    • 子類在重寫父類方法時,需要在方法前加 override 關(guān)鍵字
    • super 關(guān)鍵字調(diào)用父類方法
    • abstract 關(guān)鍵字定義抽象類,interface 關(guān)鍵字定義接口。
    • 普通情況下,類沒有靜態(tài)方法,如果需要靜態(tài)發(fā)發(fā)或所有靜態(tài)屬性,需要用伴隨對象 companion object,相當于獨立于類開辟了一塊空間,所有對象共有。
  • Properties and Fields
    • val 修改不可更改的屬性
    • var 修飾可以更改的屬性
    • lateinit 修飾延遲加載的屬性,在需要用的時候才初始化。
  • Data Class
    • 有些時候,創(chuàng)建類就是為了讓其可以有幾個屬性,Java 中的 Bean 對象,而在 Kotlin 中,將這種數(shù)據(jù)結(jié)構(gòu)單獨定義為 data class
    • 其中會自動創(chuàng)建一些方法,如 copy()equals()
    • 我們在后面會有主題講解 data class
圖片

data class 可以很簡單的定義數(shù)據(jù)結(jié)構(gòu),并且其中自動包含了 get/set 方法,易讀且方便。

Task 8:Nullable type

圖片

Java 中經(jīng)常需要判空,如果不為空,獲取其中某些屬性值等,寫起來不優(yōu)雅,且不安全,Kotlin 中采用了更簡單的方式。

圖片

注意,這并不意味著 Kotlin 中就完全不需要關(guān)心變量為空引起的錯誤,我們后面會具體詳細說明一些 Java 代碼轉(zhuǎn)成 Kotlin 或直接 寫 Kotlin 代碼引起的一些錯誤,是一個需要填的坑。

Task 9:Smart casts

圖片

自動類型轉(zhuǎn)換是一個很聰明的特性。Kotlin 中沒有 switch-case,代替它的是 when,這里的例子是判斷 expr 具體是哪一種類型,這里可以展現(xiàn)出 Kotlin 強大的是在判斷一個對象是哪一種類型后,后面這個變量直接轉(zhuǎn)換成該類型,而不像 Java 中需要強轉(zhuǎn)。

圖片

Task 10:Extension functions

圖片

擴展函數(shù)。有些時候,某些類功能不夠強大,你希望擴展這個類,而你又不能修改該類的源代碼,如果是 Java,你可能需要繼承自功能不夠強大的類,添加新方法,然后使用新的類,這比較麻煩,也可能會因為需求的變更,這個類越來越不好維護。Kotlin 的方式是在不修改原來的類的代碼的情況下,擴展方法出新的方法,使用起來就像是在原來的類中添加了新的方法。

圖片

在沒有修改 Int 和 Pair 對象的情況下,他們都有了轉(zhuǎn)成有理數(shù)的方法了。

Task 11:Object expressions

圖片

對象表達式。Kotlin 的對象表達式與 Java 中的匿名內(nèi)部類差不多。有些情況只用一次的對象,沒必要寫成一個類。

圖片

簡化代碼改為 Lambda 表達式:

圖片

再簡化代碼,使用 Kotlin stdlib:

圖片

了解越多 Kotlin stdlib 中的方法,寫代碼越快,效率越高。

Task 12:Extension functions on collections

Kotlin 在集合類中做了大量的優(yōu)化,提供了大量的有用的方法,這里排序可以直接調(diào)用。

/**
 * Returns a list of all elements sorted descending according to their natural sort order.
 */
public fun <T : Comparable<T>> Iterable<T>.sortedDescending(): List<T> {
    return sortedWith(reverseOrder())
}

Kotlin 提供了自然序的升序降序排序方法:sortedDescending

圖片

Task 13:Comparison

圖片

我們定義了一個類 MyDate,我們現(xiàn)在直接用 < 對這個類的對象比較大小。我們需要對這個對象定義比較大小操作符,則只需重寫 compareTo 方法就可以了。

圖片

Task 14:Task In range

圖片

有了上面的經(jīng)驗,這里只需了解 in 操作符是調(diào)用的 contains 方法就可以了:

圖片

簡寫成(其中 .. 就是執(zhí)行 rangeTo 方法):

圖片

Task 15:Range to

圖片
圖片
圖片

我們在上一個任務(wù)中,有編譯器自動提示使用了 in - .. 操作符,我們點進去看看,發(fā)現(xiàn)其實就是執(zhí)行了 rangeTo 方法,這一個任務(wù)中,我們就需要實現(xiàn) rangeTo 方法就可以,rangeTo 方法返回值是 ClosedRange,而 ClosedRange 需要對象實現(xiàn)比較大小 compareTo 方法,恰好我們上一節(jié)中已經(jīng)在 MyData 中實現(xiàn)了 compareTo,恰好 DateRange 實現(xiàn)了 ClosedRange,因此我們可以直接使用,結(jié)果如下:

圖片

這個任務(wù)比較復(fù)雜,但是邏輯是很清晰的。

Task 16:For loop

圖片

For 循環(huán)需要迭代器,DateRange 需要實現(xiàn) Iterable,則問題就簡單了:

圖片

當然,可以優(yōu)化代碼:

圖片

For 循環(huán)就是迭代器的遍歷。

Task 17:Operators overloading

圖片

+ 操作符,其實就是實現(xiàn) plus 方法。* 操作符就是實現(xiàn) times 方法

圖片

Task 18:Destructuring declarations

圖片

可以將對象屬性值賦值給其他對象,要求這個對象必須由 data 修飾。

圖片

Task 19:Invoke

[圖片上傳失敗...(image-63c82c-1523208961981)]

圖片

小結(jié)

本文主要了解了一些 Kotlin 的具體實現(xiàn)方法,對真正寫 Kotlin 程序極有好處。

到這里,我們完成了一半的任務(wù),為避免篇幅過長,下面的任務(wù)我們在下一篇文章中繼續(xù)討論。


如果有一天你覺得過的舒服了,你就要小心了!歡迎關(guān)注我的公眾號:我是任玉琢

qrcode_for_gh_45402a07f7d9_258
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容