[基礎篇]Kotlin第三講-擴展函數及其他

集合的創(chuàng)建與遍歷

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

創(chuàng)建集合

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接口提供了增加和刪除集合元素的能力。

創(chuàng)建不可變集合

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

創(chuàng)建可變集合

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繼續(xù)膨脹。

在Java里,我們使用靜態(tài)方法。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等開發(fā)工具會為你自動import)

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

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

如果我想更改調用靜態(tài)方法的容器類的類名為StringUtils,則需要在kotlin文件里添加

@file:JvmName("StringUtils")

這時候調用形式如下:

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

頂層屬性

counter就是頂層屬性,等效于容器類的靜態(tài)成員變量,即Java里如下寫法

public static final int counter = 0;    

拓展函數與屬性

拓展函數基本使用

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

每次調用上述實現的joinToString方法傳入四個參數,有點多,我希望減少入參數量。StringUtils是工具類類名,工具類類名在整個調用過程中是不夠高效的。達到優(yōu)雅的途徑之一就是做到高效而簡潔。這個工具類具體是什么名字并不會影響這個函數的輸入和輸出,這個類名的意義是作為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("_", "[", "]")

導入范圍

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

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

import sugarya.chapter3.joinToString as jts

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

擴展函數的本質和特性

其實,Kotlin把上述代碼翻譯到JVM上運行時。拓展函數的本質是:把接收者對象作為第一個入參的函數。通常我們在頂層位置定義擴展函數,這樣擴展函數就能被其他包的文件調用。因此,擴展函數并沒有改變接收者類里的代碼,擴展函數并不是類的一部分,它是聲明在類之外的,卻能像成員變量那般使用。

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

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

對于第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是拓展函數,轉化為靜態(tài)方法是Example2_4.move(animal),所以,move方法調用的就是Animal類下的move。

擴展屬性

擴展屬性是對擴展函數能力的弱化/簡化使用。相當于Java里第一個參數是接收者對象的靜態(tài)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)

三重引號的字符串

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

看一個佛祖鎮(zhèn)樓的例子

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

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

小結

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

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

相關閱讀更多精彩內容

友情鏈接更多精彩內容