前言
具體 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)容,我們可以看到得到如下一些信息:
- Kotlin 文件后綴為
.kt - Kotlin 文件不需要定義類,可以直接定義方法
- 簡單的方法可以省略大括號,直接在
=后寫返回值
每一個 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:


我們在這里注意幾個問題:
- 函數(shù)的定義,用 fun 關(guān)鍵字
- 參數(shù)先寫參數(shù)名,再寫參數(shù)類型,中間用冒號
:分割 - 方法參數(shù)后和大括號中間需要寫函數(shù)返回值類型,用
:說明 - 創(chuàng)建對象沒有
new關(guān)鍵字,直接類名后跟括號創(chuàng)建新對象 - 屬性定義用 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
- 有些時候,創(chuàng)建類就是為了讓其可以有幾個屬性,Java 中的 Bean 對象,而在 Kotlin 中,將這種數(shù)據(jù)結(jié)構(gòu)單獨定義為

用 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)注我的公眾號:我是任玉琢
