[基礎(chǔ)篇]Kotlin第三講-擴(kuò)展函數(shù)及其他

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

Kotlin沒有采用它自己的集合類鳖谈,而是采用標(biāo)準(zhǔn)的Java集合類紧卒。大部分Kotlin的標(biāo)準(zhǔn)庫是由Java類的拓展函數(shù)組成的。

創(chuàng)建集合

Kotlin中對集合增加了一個新的接口MutableList,實(shí)現(xiàn)該接口的集合是可變集合涮拗。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")

參數(shù)

Kotlin的函數(shù)比Java函數(shù)強(qiáng)大的地方之一是入?yún)⒖梢杂心J(rèn)值,即默認(rèn)參數(shù)三幻;

在Kotlin調(diào)用函數(shù)時就漾,可以指定入?yún)⒌拿Q,即命名參數(shù)念搬;

與Java不同抑堡,Koltin表示可變參數(shù),不是參數(shù)后面加三個點(diǎn)朗徊,首妖,而是在入?yún)⑶凹觱ararg關(guān)鍵詞即可。Kotlin中存在一個展開運(yùn)算符 -- *(星號)爷恳,它和可變參數(shù)搭配使用有缆;作用是把一個數(shù)組展開成可變參數(shù)傳入

詳細(xì)說明,可看這篇文章Kotlin里的輸入?yún)?shù)

頂層函數(shù)與屬性

  1. 很多代碼并不能歸屬到任何一個類中温亲,有時一個操作對應(yīng)兩個不同的類的對象棚壁,而且重要性相差無幾。
  2. 有時存在一個基本的對象栈虚,但不想通過實(shí)例函數(shù)來添加操作袖外,讓它的API繼續(xù)膨脹。

在Java里魂务,我們使用靜態(tài)方法在刺。Kotlin里沒有static修飾詞,它一種方式头镊,使用頂層函數(shù)來實(shí)現(xiàn)相同的效果蚣驼。

頂層函數(shù)

實(shí)現(xiàn)一個功能,把集合中元素添加前綴相艇,后綴颖杏,用分隔符間隔展示

在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()
}

頂層函數(shù)是包內(nèi)成員,包內(nèi)直接訪問坛芽。若包外訪問留储,需要import(IDEA等開發(fā)工具會為你自動import)

頂層函數(shù)是都是靜態(tài)函數(shù)翼抠,默認(rèn)函數(shù)所在的文件名加KT作為容器類,比如上述joinToString方法是在Example3_2文件名下的頂層函數(shù)获讳,在Java里調(diào)用是時

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

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

@file:JvmName("StringUtils")

這時候調(diào)用形式如下:

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

頂層屬性

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

public static final int counter = 0;    

拓展函數(shù)與屬性

拓展函數(shù)基本使用

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

每次調(diào)用上述實(shí)現(xiàn)的joinToString方法傳入四個參數(shù)量愧,有點(diǎn)多,我希望減少入?yún)?shù)量帅矗。StringUtils是工具類類名偎肃,工具類類名在整個調(diào)用過程中是不夠高效的。達(dá)到優(yōu)雅的途徑之一就是做到高效而簡潔浑此。這個工具類具體是什么名字并不會影響這個函數(shù)的輸入和輸出累颂,這個類名的意義是作為joinToString容器的標(biāo)示,如果能把其中一個入?yún)⒚鳛轭惷菥悖@個入?yún)⑼瑫r做兩件事:傳入自身到函數(shù)體里紊馏,作為調(diào)用的句柄名。

強(qiáng)大的Kotlin為我們實(shí)現(xiàn)了這樣的能力:擴(kuò)展函數(shù)蒲犬。把上述方法生命為一個拓展函數(shù)瘦棋,如下所示:

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)
}

接收者類型:函數(shù)名前的類,上例Collection就是該擴(kuò)展函數(shù)的接收者類型

接收者對象:接收者類的實(shí)例暖哨,上例this.withIndex方法的this指代的就是Collection對象赌朋,是該擴(kuò)展函數(shù)接收者對象

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

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

導(dǎo)入范圍

需要進(jìn)行導(dǎo)入擴(kuò)展函數(shù)才能生效篇裁,好在開發(fā)工具為我們自動導(dǎo)入了沛慢。如果定義的擴(kuò)展函數(shù)所在的類和其接收者類型的類在一個包下,可以不需要顯式導(dǎo)入达布。

有一種情況团甲,如果你定義的擴(kuò)展函數(shù)名和其他包里定義的函數(shù)名相同,你需要導(dǎo)入全類名以示區(qū)分黍聂。還有另一種方式躺苦,通過as在導(dǎo)入的地方重命名擴(kuò)展函數(shù)名

import sugarya.chapter3.joinToString as jts

這時候,就可以調(diào)用jts就相當(dāng)于調(diào)用joinToString方法

擴(kuò)展函數(shù)的本質(zhì)和特性

其實(shí)产还,Kotlin把上述代碼翻譯到JVM上運(yùn)行時匹厘。拓展函數(shù)的本質(zhì)是:把接收者對象作為第一個入?yún)⒌暮瘮?shù)。通常我們在頂層位置定義擴(kuò)展函數(shù)脐区,這樣擴(kuò)展函數(shù)就能被其他包的文件調(diào)用愈诚。因此,擴(kuò)展函數(shù)并沒有改變接收者類里的代碼,擴(kuò)展函數(shù)并不是類的一部分炕柔,它是聲明在類之外的酌泰,卻能像成員變量那般使用。

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

  1. 擴(kuò)展函數(shù)不能訪問私有或者受保護(hù)的成員,因?yàn)榻邮照邔ο笾皇庆o態(tài)方法的一個入?yún)⒒逗伲@個入?yún)⒂写蟮脑L問能力衰琐,擴(kuò)展函數(shù)就是多大訪問能力。
  2. 擴(kuò)展函數(shù)不能被接收者類的子類重寫/繼承际插。前面說了,擴(kuò)展函數(shù)只是靜態(tài)方法显设,并不是真實(shí)的接收者里的成員框弛,自然也就無法重寫了。

對于第2點(diǎn)的理解捕捂,我們舉一個例子

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()

輸出結(jié)果:

animal move

animal.move是拓展函數(shù)瑟枫,轉(zhuǎn)化為靜態(tài)方法是Example2_4.move(animal),所以,move方法調(diào)用的就是Animal類下的move指攒。

擴(kuò)展屬性

擴(kuò)展屬性是對擴(kuò)展函數(shù)能力的弱化/簡化使用慷妙。相當(dāng)于Java里第一個參數(shù)是接收者對象的靜態(tài)getter方法和setter方法。擴(kuò)展函數(shù)和擴(kuò)展屬性搭配使用允悦,在擴(kuò)展函數(shù)里訪問擴(kuò)展屬性膝擂。舉個例子

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

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

擴(kuò)展函數(shù)的應(yīng)用

看幾個擴(kuò)展函數(shù)的應(yīng)用例子

分割字符串

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

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

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

java里split()方法入?yún)⒌淖址硎镜恼齽t表達(dá)式架馋,在正則表達(dá)式里“.”表示任意字符,所以全闷,如果照上面所寫叉寂,返回為空,找不到字符总珠。

使用Java正確實(shí)現(xiàn)是:

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

Kotlin在此基礎(chǔ)上屏鳍,通過擴(kuò)展函數(shù)擴(kuò)展字符串方法,通過默認(rèn)參數(shù)實(shí)現(xiàn)重載效果局服。

/**
 * 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實(shí)現(xiàn)

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

Kotlin里用Regex類表示正則钓瞭,使用正則實(shí)現(xiàn)如下

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代碼實(shí)現(xiàn)

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里佳鳖,函數(shù)的最小的作用域是在一個類里(private修飾的方法)霍殴,而Kotlin引入局部函數(shù)--允許在函數(shù)里定義一個函數(shù),讓函數(shù)(方法)的最小作用域降到一個函數(shù)體里系吩。提供更小粒度的復(fù)用来庭,這樣有什么意義呢?

這樣是有意義的穿挨。

沒有局部函數(shù)的特性的Java語言里月弛,對方法最小作用域的組織方式是這樣的:一個復(fù)雜的類里有很多方法,當(dāng)方法A里的代碼行數(shù)很多時科盛,通常拆分出幾個新的方法a1帽衙,a2,a3等等贞绵,這些新的方法之間如果存在整體的邏輯關(guān)系厉萝,就能組合成一個內(nèi)部類,a1榨崩,a2谴垫,a3是該內(nèi)部類的方法。直接在A里新建內(nèi)部類并調(diào)用即可母蛛。外部類的其他方法比如方法B也能方便的調(diào)用翩剪。

Kotlin局部函數(shù)提供了比上述Java更細(xì)致的代碼組織方式:如果我們只在一個方法A里多次用到,這時候在方法A里彩郊,定義a1前弯,a2,a3秫逝,在方法A里多次使用方法a1博杖,a2,a3筷登。這種方式相較于上面的內(nèi)部類組織方式剃根,帶來的益處是降低定義內(nèi)部類帶來的語法開銷。

對于什么時候引入局部函數(shù)前方,我們有了下述認(rèn)識:
當(dāng)需要在方法粒度上多次調(diào)用一段邏輯時狈醉。具體的場景有,登錄驗(yàn)證惠险,表單數(shù)據(jù)校驗(yàn)苗傅。

中綴調(diào)用

  1. 對只有一個參數(shù)的函數(shù)使用中綴調(diào)用
  2. 中綴調(diào)用的函數(shù),需要對其使用inflix修飾符
  3. 中綴不僅適用于成員函數(shù)也適用于擴(kuò)展函數(shù)

舉個中綴的例子

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

上面的中綴調(diào)用是怎么定義呢班巩?

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

三重引號的字符串

三重引號字符串不僅在于避免轉(zhuǎn)義符渣慕,而且可以包含任何字符嘶炭,包括換行符。

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

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

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

小結(jié)

這是Kotlin實(shí)戰(zhàn)第三章涉及的所有知識點(diǎn)逊桦,結(jié)合自己的理解整理歸納成本篇文章眨猎。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市强经,隨后出現(xiàn)的幾起案子睡陪,更是在濱河造成了極大的恐慌,老刑警劉巖匿情,帶你破解...
    沈念sama閱讀 221,406評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件兰迫,死亡現(xiàn)場離奇詭異,居然都是意外死亡炬称,警方通過查閱死者的電腦和手機(jī)汁果,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,395評論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來玲躯,“玉大人据德,你說我怎么就攤上這事「幔” “怎么了晋控?”我有些...
    開封第一講書人閱讀 167,815評論 0 360
  • 文/不壞的土叔 我叫張陵汞窗,是天一觀的道長姓赤。 經(jīng)常有香客問我,道長仲吏,這世上最難降的妖魔是什么不铆? 我笑而不...
    開封第一講書人閱讀 59,537評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮裹唆,結(jié)果婚禮上誓斥,老公的妹妹穿的比我還像新娘。我一直安慰自己许帐,他們只是感情好劳坑,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,536評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著成畦,像睡著了一般距芬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上循帐,一...
    開封第一講書人閱讀 52,184評論 1 308
  • 那天框仔,我揣著相機(jī)與錄音,去河邊找鬼拄养。 笑死离斩,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播跛梗,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼寻馏,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了茄袖?” 一聲冷哼從身側(cè)響起操软,我...
    開封第一講書人閱讀 39,668評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎宪祥,沒想到半個月后聂薪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,212評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蝗羊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,299評論 3 340
  • 正文 我和宋清朗相戀三年藏澳,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片耀找。...
    茶點(diǎn)故事閱讀 40,438評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡翔悠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出野芒,到底是詐尸還是另有隱情蓄愁,我是刑警寧澤,帶...
    沈念sama閱讀 36,128評論 5 349
  • 正文 年R本政府宣布狞悲,位于F島的核電站撮抓,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏摇锋。R本人自食惡果不足惜丹拯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,807評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望荸恕。 院中可真熱鬧乖酬,春花似錦、人聲如沸融求。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,279評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽生宛。三九已至县昂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間茅糜,已是汗流浹背七芭。 一陣腳步聲響...
    開封第一講書人閱讀 33,395評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蔑赘,地道東北人狸驳。 一個月前我還...
    沈念sama閱讀 48,827評論 3 376
  • 正文 我出身青樓预明,卻偏偏與公主長得像,于是被迫代替她去往敵國和親耙箍。 傳聞我的和親對象是個殘疾皇子撰糠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,446評論 2 359

推薦閱讀更多精彩內(nèi)容