【基礎篇】Kotlin第三講-擴展函數和其他

集合的創建與遍歷

Kotlin沒有采用它自己的集合類,而是採用標準的Java集合類。大部分Kotlin的標準庫是由Java類的拓展函數組成的。

創建集合

Kotlin中對集合增加了一個新的接口MutableList,實現該接口的集合是可變集合。Kotlin中,集合分爲可變集合和不可變集合。

public interface MutableList<E> : List<E>, MutableCollection<E> {

    override fun add(element: E): Boolean

    override fun remove(element: E): Boolean

    override fun addAll(elements: Collection<E>): Boolean

    public fun addAll(index: Int, elements: Collection<E>): Boolean

    override fun removeAll(elements: Collection<E>): Boolean
    override fun retainAll(elements: Collection<E>): Boolean
    override fun clear(): Unit

    public operator fun set(index: Int, element: E): E

    public fun add(index: Int, element: E): Unit

    public fun removeAt(index: Int): E

    override fun listIterator(): MutableListIterator<E>

    override fun listIterator(index: Int): MutableListIterator<E>

    override fun subList(fromIndex: Int, toIndex: Int): MutableList<E>
}

MutableList接口提供了增加和刪除集合元素的能力。

創建不可變集合

val list = listOf<String>("a", "b", "c")
val letter = list[0]
var list1 = listOfNotNull<Int>(1, 4, 8)

創建可變集合

val list2 = arrayListOf<Int>(1, 2, 3, 4)
list2.set(0, 10)
list2[0] = 10
list2.add(5)
println("list2 = $list2")

val list3 = mutableListOf("a", "b", "c")
list3.add("d")
println("e = $list3")
println("last element = ${list3.last()}")

val list4 = mutableMapOf<String, String>("1" to "A", "2" to "B")
val list5 = mutableSetOf<String>("B", "C", "D")

參數

Kotlin的函數比Java函數強大的地方之一是入參可以有默認值,即默認參數;

在Kotlin調用函數時,可以指定入參的名稱,即命名參數;

與Java不同,Koltin表示可變參數,不是參數後面加三個點,,而是在入參前加vararg關鍵詞即可。Kotlin中存在一個展開運算符 – *(星號),它和可變參數搭配使用;作用是把一個數組展開成可變參數傳入

詳細說明,可看這篇文章Kotlin裏的輸入參數

頂層函數與屬性

  1. 很多代碼並不能歸屬到任何一個類中,有時一個操作對應兩個不同的類的對象,而且重要性相差無幾。
  2. 有時存在一個基本的對象,但不想通過實例函數來添加操作,讓它的API繼續膨脹。

在Java裏,我們使用靜態方法。Kotlin裏沒有static修飾詞,它一種方式,使用頂層函數來實現相同的效果。

頂層函數

實現一個功能,把集合中元素添加前綴,後綴,用分隔符間隔展示

在kt類裏直接寫

const val counter: Int = 0

fun <T> joinToString(
        collection: Collection<T>,
        separator: String,
        prefix: String,
        postfix: String
                                ): String {
    val sb = StringBuffer(prefix)
    for ((index, element) in collection.withIndex()) {
        if (index > 0) {
            sb.append(separator)
        }
        sb.append(element)
    }
    sb.append(postfix)

    return sb.toString()
}

頂層函數是包內成員,包內直接訪問。若包外訪問,需要import(IDEA等開發工具會爲你自動import)

頂層函數是都是靜態函數,默認函數所在的文件名加KT作爲容器類,比如上述joinToString方法是在Example3_2文件名下的頂層函數,在Java裏調用是時

Example3_2Kt.joinToString(collection, ",", "[", "]");

如果我想更改調用靜態方法的容器類的類名爲StringUtils,則需要在kotlin文件裏添加

@file:JvmName("StringUtils")

這時候調用形式如下:

StringUtils.joinToString(collection, "," , "[", "]");

頂層屬性

counter就是頂層屬性,等效於容器類的靜態成員變量,即Java裏如下寫法

public static final int counter = 0;    

拓展函數與屬性

拓展函數基本使用

StringUtils.joinToString(collection, "," , "[", "]");

每次調用上述實現的joinToString方法傳入四個參數,有點多,我希望減少入參數量。StringUtils是工具類類名,工具類類名在整個調用過程中是不夠高效的。達到優雅的途徑之一就是做到高效而簡潔。這個工具類具體是什麼名字並不會影響這個函數的輸入和輸出,這個類名的意義是作爲joinToString容器的標示,如果能把其中一個入參名作爲類名,這個入參同時做兩件事:傳入自身到函數體裏,作爲調用的句柄名。

強大的Kotlin爲我們實現了這樣的能力:擴展函數。把上述方法生命爲一個拓展函數,如下所示:

fun <T> Collection<T>.joinToString(separator: String = ",", prefix: String = "(", postfix: String = ")"){
    val sb = StringBuilder()
    sb.append(prefix)
    for((index, element) in this.withIndex()){
        if(index > 0){
            sb.append(separator)
        }
        sb.append(element.toString())
    }
    sb.append(postfix)
}

接收者類型:函數名前的類,上例Collection就是該擴展函數的接收者類型

接收者對象:接收者類的實例,上例this.withIndex方法的this指代的就是Collection對象,是該擴展函數接收者對象

這時候我們要使用joinToString方法,變成這樣用了

val list3 = mutableListOf("a", "b", "c")
list3.joinToString("_", "[", "]")

導入範圍

需要進行導入擴展函數才能生效,好在開發工具爲我們自動導入了。如果定義的擴展函數所在的類和其接收者類型的類在一個包下,可以不需要顯式導入。

有一種情況,如果你定義的擴展函數名和其他包裏定義的函數名相同,你需要導入全類名以示區分。還有另一種方式,通過as在導入的地方重命名擴展函數名

import sugarya.chapter3.joinToString as jts

這時候,就可以調用jts就相當於調用joinToString方法

擴展函數的本質和特性

其實,Kotlin把上述代碼翻譯到JVM上運行時,就是把擴展函數轉成靜態方法的。拓展函數的本質是:把接收者對象作爲第一個入參的靜態方法接收者對象看成靜態方法的一個入參。因此,擴展函數並沒有改變接收者類裏的代碼,擴展函數並不是類的一部分,它是聲明在類之外的,卻能像成員變量那般使用。

像成員變量那般使用,擴展函數和成員變量不是一回事,它們之間是有區別的

  1. 擴展函數不能訪問私有或者受保護的成員,因爲接收者對象只是靜態方法的一個入參,這個入參有大的訪問能力,擴展函數就是多大訪問能力。
  2. 擴展函數不能被接收者類的子類重寫/繼承。前面說了,擴展函數只是靜態方法,並不是真實的接收者裏的成員,自然也就無法重寫了。

對於第2點的理解,我們舉一個例子

class Person(name: String, var age: Int) : Animal(name)

//拓展定義是寫在Example2_4.Kt文件裏
fun Animal.move(){
    println("animal move")
}

fun Person.move(){
    println("Person move")
}

val animal: Animal = Person("Kotlin", 5)
animal.move()

輸出結果:

animal move

animal.move是拓展函數,轉化爲靜態方法是Example2_4.move(animal),所以,move方法調用的就是Animal類下的move。

擴展屬性

擴展屬性是對擴展函數能力的弱化/簡化使用。相當於Java裏第一個參數是接收者對象的靜態getter方法和setter方法。擴展函數和擴展屬性搭配使用,在擴展函數裏訪問擴展屬性。舉個例子

val Animal.length: Int get() = this.name.length * 10

fun Animal.move(){
    println("animal move ${this.length}")
}

擴展函數的應用

看幾個擴展函數的應用例子

分割字符串

有一個字符串“ab.cd12.ef”,需要分割成三部分:ab, cd12, ef

使用Java,我們很容易寫成這樣

String msg = "ab.cd12.ef";
String[] strings = msg.split(".");

java裏split()方法入參的字符串表示的正則表達式,在正則表達式裏“.”表示任意字符,所以,如果照上面所寫,返回爲空,找不到字符。

使用Java正確實現是:

String msg = "ab.cd12.ef";
String[] strings = msg.split("\\.");

Kotlin在此基礎上,通過擴展函數擴展字符串方法,通過默認參數實現重載效果。

/**
 * Splits this char sequence to a list of strings around occurrences of the specified [delimiters].
 *
 * @param delimiters One or more strings to be used as delimiters.
 * @param ignoreCase `true` to ignore character case when matching a delimiter. By default `false`.
 * @param limit The maximum number of substrings to return. Zero by default means no limit is set.
 *
 * To avoid ambiguous results when strings in [delimiters] have characters in common, this method proceeds from
 * the beginning to the end of this string, and matches at each position the first element in [delimiters]
 * that is equal to a delimiter in this instance at that position.
 */
public fun CharSequence.split(vararg delimiters: String, ignoreCase: Boolean = false, limit: Int = 0): List<String> {
    if (delimiters.size == 1) {
        val delimiter = delimiters[0]
        if (!delimiter.isEmpty()) {
            return split(delimiter, ignoreCase, limit)
        }
    }

    return rangesDelimitedBy(delimiters, ignoreCase = ignoreCase, limit = limit).asIterable().map { substring(it) }
}

Kotlin實現

"ab.cd12.ef"split(".")

Kotlin裏用Regex類表示正則,使用正則實現如下

val regex = Regex("\\.")
val result = "ab.cd12.ef".split(regex.toPattern())

解析字符串在Kotlin變得更容易了,除了split,Kotlin還提供了其他方法,再看一個例子

解析文件路徑

解析一個文件路徑:“/Users/mine/Documents/MyDocument/Photoes/546294_308008399296566_779316797_n.jpg”,獲取目錄路徑,文件名,文件拓展名

Kotlin代碼實現

val msg = "/Users/mine/Documents/MyDocument/Photoes/546294_308008399296566_779316797_n.jpg"
val dirPath = msg.substringBeforeLast("/")
val filePath = msg.substringAfterLast("/")
val fileName = filePath.substringBeforeLast(".")
val extendName = filePath.substringAfterLast(".")

println("directory path = $dirPath, fileName = $fileName, extendName = $extendName")

輸出:

directory path = /Users/mine/Documents/MyDocument/Photoes, fileName = 546294_308008399296566_779316797_n, extendName = jpg

局部屬性

在Java裏,函數的最小的作用域是在一個類裏(private修飾的方法),而Kotlin引入局部函數–允許在函數裏定義一個函數,讓函數(方法)的最小作用域降到一個函數體裏。提供更小粒度的複用,這樣有什麼意義呢?

這樣是有意義的。

沒有局部函數的特性的Java語言裏,對方法最小作用域的組織方式是這樣的:一個複雜的類裏有很多方法,當方法A裏的代碼行數很多時,通常拆分出幾個新的方法a1,a2,a3等等,這些新的方法之間如果存在整體的邏輯關係,就能組合成一個內部類,a1,a2,a3是該內部類的方法。直接在A裏新建內部類並調用即可。外部類的其他方法比如方法B也能方便的調用。

Kotlin局部函數提供了比上述Java更細緻的代碼組織方式:如果我們只在一個方法A裏多次用到,這時候在方法A裏,定義a1,a2,a3,在方法A裏多次使用方法a1,a2,a3。這種方式相較於上面的內部類組織方式,帶來的益處是降低定義內部類帶來的語法開銷。

對於什麼時候引入局部函數,我們有了下述認識:
當需要在方法粒度上多次調用一段邏輯時。具體的場景有,登錄驗證,表單數據校驗。

中綴調用

  1. 對只有一個參數的函數使用中綴調用
  2. 中綴調用的函數,需要對其使用inflix修飾符
  3. 中綴不僅適用於成員函數也適用於擴展函數

舉箇中綴的例子

val pair: Pair<String, String> = "a" to2 "A"

上面的中綴調用是怎麼定義呢?

infix fun <T, V> T.to2(v: V): Pair<T, V> = Pair(this, v)

三重引號的字符串

三重引號字符串不僅在於避免轉義符,而且可以包含任何字符,包括換行符。

看一個佛祖鎮樓的例子

    val bless = """
                   _ooOoo_
                  o8888888o
                  88" . "88
                  (| -_- |)
                  O\  =  /O
               ____/`---'\____
             .'  \\|     |//  `.
            /  \\|||  :  |||//  \
           /  _||||| -:- |||||-  \
           |   | \\\  -  /// |   |
           | \_|  ''\---/''  |   |
           \  .-\__  `-`  ___/-. /
         ___`. .'  /--.--\  `. . __
      ."" '<  `.___\_<|>_/___.'  >'"".
     | | :  `- \`.;`\ _ /`;.`/ - ` : | |
     \  \ `-.   \_ __\ /__ _/   .-` /  /
======`-.____`-.___\_____/___.-`____.-'======
                   `=---='
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
         佛祖保佑       永無BUG
         """
    println(bless)     

這樣控制檯按原樣格式輸出佛祖圖

小結

這是Kotlin實戰第三章涉及的所有知識點,結合自己的理解整理歸納成本篇文章。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章