1. 程式人生 > >Kotlin筆記(三)

Kotlin筆記(三)

1 在Kotlin中建立集合

val set = setOf(1,2,3,4)//這個地方返回的是一個數組
val set2 = hashSetOf(1,2,3,4)
可以用類似的方法建立list或者map:
val list = arrayListOf(1,2,3,4,5)
val map = hashMapOf(1 to "one",2 to "two",3 to "three",4 to "four",5 to "five")
注意,to並不是一個特殊的結構,而是一個普通的結構。在稍候會探討它
println(set.javaClass)
println(list.javaClass)
println(map.javaClass)
列印結果為:

Kotlin中並沒有採用它自己的集合類,而是採用的標準Java集合類。這樣對Java開發者是一個好訊息,現在所掌握的Java集合知識在Kotlin中一樣可以使用。為什麼Kotlin沒有自己專門的集合類呢?那是因為使用標準的Java集合類,Kotlin可以更容易與Java程式碼交換。當從Kotlin呼叫Java方法的時候,不用轉換它的集合類來匹配Java的類,反之亦然。

2 讓函式更好用
我們知道怎麼建立集合了,讓我們列印它的內容

val list = arrayListOf(1,2,3,4,5)
println(list)
列印結果:

如果需要用分號分給每個元素,然後用括號括起來,而不是採用方括號:(1;2;3;4;5)。解決這個問題,Java專案會使用第三方的類庫,而在Kotlin中,它的標準庫中有專門的函式處理這種情況。

/**
 * 擴充套件函式
 * 使用JVMOverload註解指示編譯器生成java過載函式,從最後一個開始省略每個引數
 *
 */
@JvmOverloads
fun <T> Iterable<T>.joinToString(separator:String = ": ",prefix:String = "{",postfix: String = "}"): String {
    val result = StringBuilder()
    result.append(prefix)
    for((index, element) in this.withIndex()){
        if(index > 0)
            result.append(separator)
        result.append(element)
    }
    result.append(postfix)
    return result.toString()
}

其實這個函式在Kotlin中是已經實現的一個擴充套件函式。這個函式式一個引數帶預設值的擴充套件函式,這樣可以避免建立過載函式。如果不傳引數,引數後面的值就會被使用;如果只是想傳入部分引數,需要使用“引數名=值“進行傳入,跟下面第二個列印一樣。

println(list.joinToString())
列印結果就是:

println(list.joinToString(separator = "  "))

2.1消除靜態工具類:頂層函式和屬性
我們都知道,在Java作為一門面向物件的語言,需要所有的程式碼都寫作類的類的函式。大多數情況下,這種方式還能行得通。但事實上,幾乎所有的大型專案,最終都有很多的程式碼並不能屬於任何一個類中。有時一個操作對應兩個不同的類的物件,而且重要性相差無幾。有時存在一個基本的物件,但你不想通過例項來新增操作,讓它的API繼續膨脹。結果就是,最終這些類將不包含任何的狀態或者例項函式,而是作為一堆靜態函式的容器。在JDK中,最適合的例子應該就是Collections了。

在Kotlin中,根本就不需要去建立這些無意義的類。相反,可以把這些函式直接放到程式碼檔案的頂層,不用從屬於任何的類。這些放在檔案頂層的函式依然是包內的成員,如果你需要從包外訪問它,則血藥import,但不再需要額外包一層。
package strings
fun joinToString(...):String{...}這個檔案編譯完生成的Java類
package strings;
public class JoinKt{
     public static String joinToString(...){...}
}
可以看到Kotlin編譯生存的類的名稱,對應於包含函式的檔案的名稱。這個檔案中的所有頂層函式編譯為這個類的靜態函式。

2.2頂層屬性
和函式一樣,屬性也可以放到檔案的頂層。在一個類的外面儲存單獨的資料片段雖然不常用,但是還是有他的價值。舉個例子,可以用var屬性來計算一些函式被執行的次數:

var opCount = 0
fun performOperation(){
    opCount ++
}
fun reportOperationCount(){
    println("Operation performed $opCount times")
}
像這個值就會被儲存到一個靜態的欄位中。
也可以在程式碼中用頂層屬性來定義常量:
val UNIX_LINE_SEPARATOR = "\n"
預設情況下,頂層屬性和其他任意的屬性一樣,是通過訪問器暴露給Java使用的(如果是val就只用一個getter,如果是var就對應一對getter和setter)。為了方便使用,如果你想要把一個常量以public static final 的屬性暴露給Java,可以用const來修飾(這個適用於所用的基本資料型別的屬性,以及String型別)。
const val UNIX_LINE_SEPARATOR = "\n"
這個等同於下面的Java程式碼
public static final String UNIX_LINE_SEPARATOR = "\n";

2.3給別人的類新增方法:擴充套件函式和屬性

Kotlin的一大特色,就是可以平滑的與現有程式碼整合。甚至,純Kotlin的專案都可以基於Java庫構建,如JDK、Android框架,以及其他的第三方框架。當你在一個現有的Java專案中整合Kotlin的時候,依然需要面臨現有程式碼目前不能轉換成Kotlin,甚至將來也不會轉成Kotlin的局面。當使用這些API的時候,如果不用重寫,就能使用到Kotlin為它帶來的方便,豈不是更好?這裡,可以用擴充套件函式來實現。
理論上來說,擴充套件函式非常簡單,他就是一個類的成員函式,不過定義在類的外面。為了方便闡釋,讓我們新增一個方法,來計算一個字串的最後一個字元

/**
 * 擴充套件函式
 */
fun String.lastChar():Char {
    return this.get(this.length-1)
}
使用的時候
println("Kotlin".lastChar())
在這個例子中,String就是接受型別,而"Kotlin"就是接受者物件。
從某種意義上說,你已經為String類添加了自己的方法。即使字串不是程式碼的一部分,也沒有類的原始碼,你仍然可以在自己的專案中根據需要去擴充套件方法。不管String類是用Java、Kotlin或者想Groovy的其他JVM語言編寫的,只要是它會編譯為Java類,你就可以為這個類新增自己的擴充套件。
咋這個擴充套件函式中,可以像其他成員函式一樣用this。而且也可以像普通的成員函式一樣省略它。
fun String.lastChar() = get(length-1)
在擴充套件函式中,可以直接訪問被擴充套件的類的其他方法和屬性,就好像是在這個類自己的方法中訪問它們一樣。注意,擴充套件函式並不允許你打破它的
封裝性。和在類的內部定義的方法不同的是,擴充套件函式不能訪問私有的或者受保護的成員。

2.3.1匯入擴充套件函式
對於你定義的一個擴充套件函式,它不會自動地在整個專案範圍內生效。相反,如果你要使用它,需要進行匯入,就像其他任何的類或者函式一樣。這是為了避免偶然性的命名衝突。Kotlin允許用和匯入類一樣的語法來匯入單個的函式:
import com.houde.second.lastChar
"Kotlin".lastChar()
當然也可以用*來匯入:
import com.houde.*
可以使用關鍵字as來修改匯入的類或者函式的名稱:
import com.houde.second.lastChar as last
"Kotlin".last()
當在不同的包中,有一些重名函式時,在匯入時給它重新命名就顯得很有必要了,這樣可以在同一個檔案中去使用它們在這種情況下,對於一般的類和函式,還有另外一個選擇:可以選擇用全名來指出這個類或者函式。對於擴充套件函式,Kotlin的語法要求你用簡短的名稱,所以,在匯入宣告的時候,關鍵字as就是解決命名衝突的唯一方式。

2.3.2從Java中呼叫擴充套件函式
實質上,擴充套件函式式靜態函式,它把呼叫物件作為了它的第一個引數。呼叫擴充套件函式,不會常見適配的物件或者任何執行時的額外消耗。這使得從Java中呼叫Kotlin的擴充套件函式變得非常簡單:呼叫這個靜態函式,然後把結束這物件作為第一個引數傳進去即可,可其他的頂層函式一樣,包含這個函式的Java類的名稱,是由這個函式宣告的檔名稱決定的。假設它宣告在一個叫做HelloKotlin5.kt的檔案中:
char c = HelloKotlin5Kt.lastChar("Java");
System.out.println(c);
這個擴充套件函式被宣告為頂層函式,所以,它將會被編譯為一個靜態函式。在Java中靜態匯入lastChar函式,就可以直接使用它了,如lastChar("Java")。

2.3.3 作為擴充套件函式的工具函式
現在可以寫一個joinToString函式的終極版本了:
/**
 * 擴充套件函式
 * 使用JVMOverload註解指示編譯器生成java過載函式,從最後一個開始省略每個引數
 *
 */
@JvmOverloads
fun <T> Iterable<T>.joinToString(separator:String = ": ",prefix:String = "{",postfix: String = "}"): String {
    val result = StringBuilder()
    result.append(prefix)
    for((index, element) in this.withIndex()){
        if(index > 0)
            result.append(separator)
        result.append(element)
    }
    result.append(postfix)
    return result.toString()
}
println(arrayListOf(1,2,3,4,5).joinToString(separator = "; ",prefix = "{",postfix = "}"))
可以給元素的集合類新增一個擴充套件函式,然後給所有的引數新增一個預設值。這樣,就可以像使用一個類的成員函式一樣,去呼叫joinToString了。
val list = arrayListOf(1,2,3,4,5)
println(list.joinToString())
因為擴充套件函式無非就是靜態函式的一個高效語法糖,可以使用更具體的型別來作為接受者型別,而不是一個類。假設你想要一個join函式,你能有字串的集合來觸發。
fun Collection<String>.join(
        separator:String = ", ",
        prefix:String = "",
        postfix: String = ""
        ) = joinToString(separator,prefix,postfix)
如果是用其他型別的物件列表來呼叫,將會報錯:
val list = arrayListOf(1,2,3,4,5)
list.join()
Error:(11, 5) Kotlin: Type mismatch: inferred type is kotlin.collections.ArrayList<Int> but Collection<String> was expected。

擴充套件函式的靜態性質也就決定了擴充套件函式不能被子類重寫。2.3.4擴充套件屬性
擴充套件屬性提供了一種方法,用來擴充套件類的API,可以用來訪問屬性,用的是屬性語法而不是函式語法。儘管它們被稱為屬性,但它們可以沒有任何狀態,因為沒有合適的地方來儲存它,不可能給現有的Java物件的例項新增額外的欄位。但有時短語法仍然是便於使用的。在上面我們定義了一個lastChar的函式,現在把它轉換成一個屬性試試。
/**
 * 擴充套件屬性
 * String類的不變的擴充套件屬性
 */
val String.lastChar: Char
get() = get(length - 1)
可以看到,和擴充套件函式一樣,擴充套件屬性也像接受者的一個普通的成員屬性一樣。這裡,必須定義getter函式,因為沒有支援欄位,因此沒有預設的getter實現。同理初始化也不可以:因為沒有地方儲存初始值。
如果在StringBulider上定義一個相同的屬性,可以置為var,因為StringBuilder內容是可變的。
/**
 * StringBuilder類的可變擴充套件屬性
 * 可變與不可變主要在於關鍵字val還是var上
 */
var StringBuilder.lastChar: Char
set(value: Char) = set(length-1,value)
get() = get(length-1)
注意:當需要Java中訪問擴充套件屬性的時候,應該顯示地呼叫它的getter函式:檔名kt.getLastChar("Java");

4處理集合:可變引數、中綴呼叫和庫支援
幾個相關語言特性:
1.可變引數關鍵字:vararg,可以用來宣告一個函式將可能有任意數量的引數
2.一箇中綴表示法,當你在呼叫一些只有一個引數的函式時,使用它會讓程式碼更加簡練
3.解構宣告,用來把單獨的組合值展開到多個變數中

4.1擴充套件Java集合的API
開始的前提是基於Kotlin中的集合與java的類相同,但對API做了擴充套件
val list = arrayListOf(1,2,3,4,5)
println(list.last())
println(list.max())

我們感興趣的是它是怎麼工作的:儘管它們是Java庫類的例項,為什麼Kotlin中能對集合有這麼多豐富的操作。現在答案很明顯:因為函式last()和max()都被宣告成了擴充套件函式。
last函式不會比String的lastChar更復雜,它是List類的一個擴充套件函式。

/**
 * Returns the last element.
 * @throws [NoSuchElementException] if the list is empty.
 */
public fun <T> List<T>.last(): T {
    if (isEmpty())
        throw NoSuchElementException("List is empty.")
    return this[lastIndex]
}

4.2可變引數:讓函式支援任意數量的引數

在呼叫一個函式來建立列表的時候,可以傳遞任意個數的引數給它:

val list = listOf(2,3,4,5,6,7)
這個函式的宣告是:
/**
 * Returns a new read-only list of given elements.  The returned list is serializable (JVM).
 * @sample samples.collections.Collections.Lists.readOnlyList
 */
public fun <T> listOf(vararg elements: T): List<T> = if (elements.size > 0) elements.asList() else emptyList()
Kotlin的可變引數與Java類似,但語法略有不同:Kotlin在該型別之後不會再使用三個點,而是在引數上使用vararg修飾符。
Kotlin和Java之間的另一個區別是,當需要傳遞的引數已經包裝在陣列中時,呼叫該函式的語法。在Java中,可以按原樣傳遞陣列,
而Kotlin則要求你顯示地解包陣列,以便每個陣列元素在函式中能作為單獨的引數來呼叫。從技術的角度來講這個功能被稱為展開運算子,
而使用的時候,不過是在對應的引數前面放一個 * :
val args2 = arrayOf(1,2,3,4,5)
val list3 = listOf(*args2)
println(list3)
列印結果為:
[1, 2, 3, 4, 5]
這個例項展示了,通過展開運算子,可以在單個呼叫中組合來自陣列的值和某些固定值。這在Java中並不支援。

4.3鍵值對的處理:中綴呼叫和結構宣告

可以使用mapOf函式來建立Map:

val map = hashMapOf(1 to "one",2 to "two",3 to "three",4 to "four",5 to "five")
這行程式碼中的單詞to不是內建的結構,而是一種特殊的函式呼叫被稱為中綴呼叫。在中綴呼叫中,沒有新增額外的分隔符,函式名稱是直接放在
目標物件名稱和引數之間的。以下兩個呼叫方式是等價的:
1 to "one"  和 1.to("one")
中綴呼叫可以與只有一個引數的函式一起使用,無論是普通的函式還是擴充套件函式。要允許使用中綴符號呼叫函式,需要使用infix修飾符來標記
它。下面是一個簡單的to函式宣告:
/**
 * to 函式宣告
 */
infix fun Any.to(other :Any) = Pair(this,other)
to函式會返回一個Pair型別的物件,Pair是Kotlin標準庫中的類,不出所料,他會用來表示一對元素。Pair和to的宣告都用到了泛型,簡單起
見,這裡我們省略了泛型。注意,可以直接用Pair的內容來初始化兩個變數:
val (number,name) = 1 to "one"
解構宣告特徵不止用於pair。例如,還可以使用map的Key和value內容來初始化兩個變數。
這也適用於迴圈,正如你在使用withIndex函式的joinToString實現中看到的:
val list = arrayListOf(1,2,3,4,5)
for((index,element) in list.withIndex()){
    println("index is $index value is $element")
}
輸出結果:

to函式式一個擴充套件函式,可以建立一對任何元素,這意味著它是泛型接受者的擴充套件:可以使用1 to "one" 、 "one" to 1 、list to list.size()等寫法。

5  字串和正則表示式的處理

Kotlin字串與Java字串完全相同。可以將在Kotlin程式碼中建立的字串傳遞給任何Java函式,也可以吧任何Kotlin標準庫函式
應用到從Java程式碼接收的字串上,而不用轉換,也不用建立附加物件。
Kotlin通過提供一系列有用擴充套件函式,使標準Java字串使用起來更加方便。此外,它還隱藏了一些令人費解的函式,添加了一些
更清晰易用的擴充套件。作為體現API差異化的第一個例子。
5.1 分割字串
我們可能對String的split方法很熟悉了。每個人使用它,但有時候會抱怨Java的split方法不適用於一個點號。