Kotlin學習與實踐 (三)fun 函數
阿新 • • 發佈:2018-01-27
意義 element 中間 類的方法 int prefix cal save tor
通過例子來學習如何高效的在Kotlin中定義使用函數。
1、命名參數、默認參數、頂層函數、擴展函數
* 展示幾種創建集合類的方法和擴展的簡單操作集合類的方法
fun createCollection() { val set = hashSetOf(1, 12, 26) println("hashSetOf -- > ${set.javaClass}") val list = arrayListOf(12, 24, 66) println("arrayListOf -- > ${list.javaClass}") val map = hashMapOf(1 to "one", 2 to "two", 3 to "three") println("hashMapOf -- > ${map.javaClass}") val list1 = listOf("dsa", "ccc", "ddd") println("listOf -- > ${list1.javaClass}") } fun easyOptCollection() { val strings = listOf("ss", "this", "is", "string ArrayList") println(strings.last()) val numbers = listOf(1, 200, 20, 30) println(numbers.max()) }
Kotlin並沒有采用它自己的集合類,而是采用標準的Java集合類,這樣Kotlin就能與Java交互。
再看一個示例:
fun demand() { val list = listOf(1, 2, 25) println(list) }
* 上面函數直接輸入 list 是調用了集合的默認的toString方法,為了動態修改輸入的樣子,下面的幾個函數是
* 自己的擴展,再擴展中探討如何讓Kotlin 的方法更簡單 更高效 更舒服
下面首先按照Java的習慣和風格定義一個自定義格式化輸出集合的方法
/** * 通過再元素中間加分隔符,在最前面加前綴,再最後面加後綴把集合轉成可輸出的String * @param collection 集合 * @param separator 分隔符 * @param prefix 前綴 * @param postfix 後綴 */ fun <T> joinToString(collection: Collection<T>, separator: String, prefix: String, postfix: String): String { val result = StringBuilder(prefix) for ((index, element) in collection.withIndex()) { if (index > 0) result.append(separator) //第一個元素之前不用加分隔符 result.append(element) } result.append(postfix) return result.toString() }
* 為了提高代碼的可讀性
* Kotlin支持 命名參數 --> 調用函數時 顯式地指定參數名稱 (牛叉的是:顯示指定名稱之後就可以打亂參數的傳遞順序了)
* 註意:為了避免混淆指明了一個參數的名稱之後,後面的參數必須都要標明名稱
* 警告:使用Kotlin 調用Java函數時,不能采用命名參數
fun callExplicitly() { val list = listOf(1, 3, 5) println(joinToString(list, prefix = "{", separator = "\\", postfix = "}")) }
* 為了避免像Java那樣過多的重載與重復
* Kotlin 支持默認參數值--> 在聲明函數的時候,指定參數的默認值,在調用的時候不傳該參數時就使用默認的參數
* 這樣就可以避免創建很多的重載函數
*
* Java中如果想調用指定默認參數的函數必須全部傳遞參數,如果想像在Kotlin中一樣使用其省略參數的調用方式就需要給
* Kotlin中聲明的 指定默認參數的函數 添加“@JvmOverloads”註解,原理是編譯器會帶有"@JvmOverloads"的方法自動生成重載函數
@JvmOverloads fun <T> joinToStringWithDefaultParams(collection: Collection<T>, separator: String = ",", prefix: String = "", postfix: String = ""): String { return joinToString(collection, separator = separator, prefix = prefix, postfix = postfix) } fun callWithDefaultParams() { val list = listOf(1, 3, 5) println(joinToStringWithDefaultParams(list)) println(joinToStringWithDefaultParams(list, "-")) println(joinToStringWithDefaultParams(list, "-", "【", "】")) println(joinToStringWithDefaultParams(list, postfix = "!")) }
在Java 中一些無法從屬任何類又可能會被很多類頻繁的調用的方法通常會抽取到專門的一個類中,以 public static final sss()最終會形成包含很多這種方法的工具類
* 在Kotlin中根本不需要去創建這樣無意義的類。
* 可以把這樣的不從屬於任何類的函數放到代碼文件的頂層,這些放在文件頂層的函數依然是包內的成員
* 如果要從外部訪問它,直接導入包就可以用,不要額外包一層類名
*
* 其實編譯器會.kt 文件編譯成Java類,類名為.kt文件名+Kt 例如:join.kt ---> JoinKt.class
* 因此在Java中調用頂層函數也很簡單直接導入編譯的包含頂層文件的類就行了
*
* 如果要改變包含Kotlin 頂層函數的文件被編譯生成的類名,需要為這個文件添加 “@JvmName”的註解,將其放到文件的開頭,位於包名的前面
* 比如本類執行的名稱“@file:JvmName("StringsFunctions")”
import com.mauiie.kotlin.chapter2fun.K4ExtendPropertyKt;
import com.mauiie.kotlin.chapter2fun.StringsFunctions;
public class JavaCallTest { public static void main(String[] args) { ArrayList<String> strings = new ArrayList<>(5); strings.add("dsada"); strings.add("adsa"); strings.add("jklj"); strings.add("dsada"); System.out.println(StringsFunctions.joinToStringWithDefaultParams(strings)); TopFunAndProperty.performOperation(); TopFunAndProperty.performOperation(); TopFunAndProperty.reportOperationCount(); System.out.println(TopFunAndProperty.getUNIX_LINE_SEPARATOR()); System.out.println(TopFunAndProperty.UNIX_LINE_SEPARATOR_CONSTANTS); System.out.println(TopFunAndProperty.getOpCount()); System.out.println(K3ExtendFunAndPropertyKt.lastChar_("this is a")); /** * 擴展函數和擴展方法在Java中調用的時候都必須顯示的調用 */ String testStr = "test"; System.out.println(K4ExtendPropertyKt.getLastChar(testStr)); System.out.println(K4ExtendPropertyKt.lastChar(testStr)); Button button = new Button(); System.out.println(button.getCurrentState()); ; }
* 在使用JDK、Android的時候,有時會面臨代碼不能轉成Kotlin的時候,Kotlin支持擴展函數讓Kotlin支持不能轉的代碼
* 理論上來說擴展函數非常簡單,它就是一個類的成員函數,不過定義在類的外面。
* 做法很簡單:把要擴展的類或接口的名稱,放到即將添加的函數前面。
* 這個類的名稱叫 “接收者類型”;用來調用這個擴展函數的那個對象叫做“接收者對象”
* 接收者類型 是由擴展函數定義的,接收者對象是該類型的一個實例
fun String.lastChar_(): Char = this.get(this.length - 1)
* String 是定義的接收者類型
* this 是定義的接收這對象 也就是String的一個實例
* 在擴展函數中,可以像其他成員函數一樣訪問類的其他變量和屬性,就好像是在這個類自己的方法中訪問他們一樣。
* 註意:擴展函數不能打破類的封裝性。???????
* 和類的成員變量不同的是,擴展函數不能訪問類的私有或者受保護的成員。
* 在擴展函數中,可以像其他成員函數一樣使用“this”,而且也可以像其他成員函數一樣省略它
fun String.easyLastChar(): Char = get(length - 1) fun main(args: Array<String>) { //定義好擴展函數之後就可以像普通的成員函數一樣去使用了 println("kotlin".lastChar_()) println("test".easyLastChar()) }
* 在Kotlin中,重寫成員函數是很平常的事情,但是,不能重寫擴展函數。
* 但是,但是, 不能重寫擴展函數
* 擴展函數並不是類的一部分,它是聲明在類之外的。盡管可以給基類和子類都分別定義一個同名的擴展函數,當這個函數被調用時
* 它是由該變量的靜態類型所決定的,而不是這個變量的運行時類型
* 註意: 如果一個類的成員函數和擴展函數有相同的簽名,成員函數往往會被優先使用。
* 記住: 如果添加一個和擴展函數一樣名字的成員函數,那麽對應類定義的消費者將會重新編譯代碼,這將會改變它的意義開始指向新的成員函數!
open class View { open fun click() = println("View clicked") } class Button : View() { override fun click() = println("Button clicked") } fun View.showOff() = println("View showOff") fun Button.showOff() = println("Button showOff") fun main(args: Array<String>) { val v: View = Button() val v2: View = View() val v3 = View() //具體調用哪個方法是由view的實際值來決定的, v.click() v2.click() v3.click() //但是, 不能重寫擴展函數 v.showOff() v2.showOff() v3.showOff() val button = Button() button.showOff() }
執行結果:
Button clicked
View clicked
View clicked
View showOff
View showOff
View showOff
Button showOff
Kotlin中直接在文件函數叫做頂層的函數,再Kotlin中看起來不從屬任何類從屬於包直接導入可以使用,但是從編譯之後的字節碼看會自動編譯xxxKT.Java,頂層函數從屬於編譯的文件類
@file:JvmName("TopFunAndProperty") package com.mauiie.kotlin.chapter2fun
/** * 這樣就聲明了一個頂層屬性 */ var opCount = 0 //會生成getter和setter /** * 聲明一個頂層函數 */ fun performOperation() { opCount++ } fun reportOperationCount() { println("Operation performed $opCount times ") } val UNIX_LINE_SEPARATOR = "\n" //只會生成getter const val UNIX_LINE_SEPARATOR_CONSTANTS = "\n" //相當於Java中的 public static final String ...l /** * 現在我們可以將joinToString 函數寫出終極狀態了 ---> 作為擴展函數的工具函數 */ fun <T> Collection<T>.joinToString( separator: String, prefix: String, postfix: String ): String { val result = StringBuilder(prefix) // withIndex 省去了this this.withIndex() for ((index, element) in withIndex()) { if (index > 0) result.append(separator) result.append(element) } result.append(postfix) return result.toString() }
2、擴展屬性
* 擴展屬性提供了一種方法,用來擴展類的API,可以用類訪問屬性,用的是屬性語法而不是函數的語法。
* 盡管它被稱為屬性,但是他們可以沒有任何狀態,因為沒有適合的地方來存儲它,不可能給現有的Java對象實例添加額外的字段。
* 但有時段語法仍然是便於使用的。
先扔出來一個擴展函數
fun String.lastChar(): Char = get(length - 1)
* 上面是為String 擴展的方法 lastChar() 現在把它轉成屬性試試
* 可以看到擴展屬性也像接收者的一個普通成員屬性一樣。
* 這裏,必須定義getter函數,因為沒有支持字段,因此沒有默認的getter實現。
* 同理,初始化也不可以:因為沒有地方可以存儲初始值
val String.lastChar: Char get() = get(length - 1) var StringBuilder.lastChar: Char get() = get(length - 1) set(value: Char) { this.setCharAt(length - 1, value) } fun main(args: Array<String>) { println("Kotlin".lastChar) val sb: StringBuilder = StringBuilder("Kotlin") sb.lastChar = ‘a‘ println(sb.lastChar) }
註意
* 如果要從Java中訪問擴展屬性,應該顯示地調用它的getter函數 K4ExtendPropertyKt.getLastChar("Java")
*對於定義的擴展函數,不會自動在整個項目中生效,使用的時候需要像其他函數和類一樣導入
* 可以使用 as 關鍵字來修改導入的類或者名稱 同樣在Java也可以使用定義好的擴展函數 參照 java.JavaCallKotlin
package com.mauiie.kotlin.chapter2fun //import com.mauiie.kotlin.chapter2fun.* import com.mauiie.kotlin.chapter2fun.lastChar_ as last val c = "Kotlin".last()
3、可變參數、中綴表示、解構聲明
* a.可變參數的關鍵字 vararg,可以用來聲明一個函數可能有任意數量的參數
* b.一個中綴表示法,當你在調用一些只有一個參數的函數時,使用它會讓代碼更簡練
* c.解構聲明,用來把一個單獨的組合值展開到多個變量中
首先看可變參數 vararg的使用:
fun kebiancanshu() { val list = listOf(1, 45, 36)
//listOf源碼 //public fun <T> listOf(vararg elements: T): List<T> = if (elements.size > 0) elements.asList() else emptyList() }
* 上面可以看在Kotlin使用了 vararg 代替了 Java中的... 聲明了可變參數
* 與Java不用的一點是如果在傳入參數是已經包裝在數組中的參數時,在Java中會原樣傳遞數組,而Kotlin則要求你顯示著解包數組,以便於每個數組元素能作為單獨的參數來調用
* 從技術角度講這個功能被稱為"展開運算符",而在使用的時候不過是在參數前面放一個 “*”
fun testVararg(args: Array<String>) {
val list = listOf("test", * args)
println(list)
}
fun main(args: Array<String>) {
testVararg(listOf("ds", "dsa", "111").toTypedArray()) //toTypedArray Returns a *typed* array containing all of the elements of this collection.
}
* 聲明map的代碼中的to,不是內置解構,而是一種特殊函數調用,被稱為中綴調用。
* 在中綴調用中,沒有添加額外的分隔符,函數名稱是直接放在目標對象名稱參數之間的。
* 1 to "one" 和 1.to("one")是等價的
fun testMap() { val map = mapOf(1 to "one", 2 to "two", 3.to("three")) // 源碼 public fun <K, V> mapOf(vararg pairs: Pair<K, V>): Map<K, V> = if (pairs.size > 0) pairs.toMap(LinkedHashMap(mapCapacity(pairs.size))) else emptyMap() val list = listOf(1.45) //這也是一種解構聲明 for ((index, element) in list.withIndex()) { println("this $index is $element") } }
* 中綴調用可以與之喲普一個參數的參數的函數一起使用,無論是普通的函數還是擴展函數。
* 要允許使用中綴符號調用函數,需要使用infix修飾符來標記它。
* 下面是一個簡單的to函數的聲明
* Pair 是Kotlin標準庫中的類,它表示已對元素。Pair和to 都使用了泛型,這裏為了簡單都省略了他們
infix fun Any.to(other: Any) = Pair(this, other) fun testPair() { val (number, name) = 1 to "one" println("this is $number and $name") }
4、局部函數
為了讓代碼更簡潔,Kotlin提供了局部函數的功能:
* 局部函數指的是 可以在函數中嵌套這些提取的函數,這樣既可以獲得所需的結構,也無須額外的語法開銷
先看下一個簡單的例子,稍後使用局部函數改造,體驗局部函數:
class User(val id: Int, val name: String, val address: String) fun saveUser(user: User) { if (user.name.isEmpty()) { throw IllegalArgumentException("Can‘t save user[${user.id}] with empty name") } if (user.address.isEmpty()) { throw IllegalArgumentException("Can‘t save user[${user.id}] with empty address") } println("save user successful!") }
* 提取局部函數避免重復
fun saveUser1(user: User) { fun validate(user: User, value: String, fieldName: String) { if (value.isEmpty()) { throw IllegalArgumentException("Can‘t save user[${user.id}] with empty $fieldName ") } } validate(user, user.name, "Name") validate(user, user.address, "address") println("save user successful!") }
* 可以在局部函數總直接訪問外部函數的參數!!
fun saveUser2(user: User) { fun validate(value: String, fieldName: String) { if (value.isEmpty()) { //可以在局部函數總直接訪問外部函數的參數!! throw IllegalArgumentException("Can‘t save user[${user.id}] with empty $fieldName ") } } validate(user.name, "Name") validate(user.address, "address") println("save user successful!") }
* 提取邏輯到擴展函數
fun User.validateBeforSave() { fun validate(value: String, fieldName: String) { if (value.isEmpty()) { throw IllegalArgumentException("Can‘t save user[${this.id}] with empty $fieldName ") } } validate(name, "Name") validate(address, "Address") }
Kotlin學習與實踐 (三)fun 函數