集合的創(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ù)與屬性
- 很多代碼并不能歸屬到任何一個類中温亲,有時一個操作對應(yīng)兩個不同的類的對象棚壁,而且重要性相差無幾。
- 有時存在一個基本的對象栈虚,但不想通過實(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ū)別的
- 擴(kuò)展函數(shù)不能訪問私有或者受保護(hù)的成員,因?yàn)榻邮照邔ο笾皇庆o態(tài)方法的一個入?yún)⒒逗伲@個入?yún)⒂写蟮脑L問能力衰琐,擴(kuò)展函數(shù)就是多大訪問能力。
- 擴(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)用
- 對只有一個參數(shù)的函數(shù)使用中綴調(diào)用
- 中綴調(diào)用的函數(shù),需要對其使用inflix修飾符
- 中綴不僅適用于成員函數(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é)合自己的理解整理歸納成本篇文章眨猎。