# kotlin 語法筆記 <br>目錄 (點擊可以跳轉) * <a href=#start>第一個kotlin程式</a> * <a href=#variable>變數</a> * <a href=#if>條件判斷式(if)</a> * <a href=#container>泛型容器(集合)</a> * <a href=#string>字串處理</a> * <a href=#integer>數字處理</a> * <a href=#function>函數</a> * <a href=#standardfunction>標準函數</a> * <a href=#file>檔案處理(IO)</a> * <a href=#exception>例外處理</a> * <a href=#class>類別(class)</a> * <a href=#generic>泛型</a> * <a href=#extensions>擴充</a> * <a href=#performance>評估效能</a> * <a href="#java">與java互動</a> ## <p id = "start">第一個kotlin程式</p> 來寫第一個kotlin程式吧 ``` fun main(args:Array<String>) { println("Hello,world!") } ``` 輸出 ``` Hello,world! ``` ## <p id = "variable">變數</p> **宣告變數方式:** 令為可修改的變數 **var 變數名稱:型態=值** 令為不可修改的變數 **val 變數名稱:型態=值** **更簡潔的方式:** 令為可修改的變數 **var 變數名稱=值** 令為不可修改的變數 **val 變數名稱=值** kotlin支援型態推斷,會自己依照值判斷變數型態 ### 解構 當有多個變數要由一個有集合性質的物件令值,可以使用這種方法 val() var() 範例: ``` fun main(args:Array<String>) { var list= listOf<Int>(1,2,3) var(num1,num2) = list println("num1:$num1") println("num2:$num2") } ``` 輸出: ``` num1:1 num2:2 ``` 當物件支援解構,代表其類別有實作operator fun componentN,N為數字,當進行解構時,會自動找這些函數來獲取回傳值 ### 常見基礎型態: * 字串 String * 字元 char * 布林值(真假值) Boolean * 整數 Int * 雙經度小數 Double * 單經度小數 Float 使用Float令值,值須特別加上f,否則一般都會先視為Double **以下三個是無法修改的容器型態** 即一旦建立就不能修改內容了 * 有順序元素集合 List * 無重複元素的集合 Set * 鍵值對集合 Map **對應可修改內容的容器型態** * MutableList * MutableSet * MutableMap 此外給的值,值不能不符合那個型態 ### String範本 更方便的變成字串 **使用用法** 在字串裡任何位置加上: $變數名 ${有回傳值的執行式} 範例 ``` fun main(args:Array<String>) { var a = 10 var str1 = "a=$a" var str2 = "a is ${if(a==10)10 else if (a==20) 20 else 0}" var str3 = "a is ${get100()}" println(str1) println(str2) println(str3) println("${get100()}") } fun get100(): Boolean { return true } ``` 輸出 ``` a=10 a is 10 a is true true ``` ### const val 儘管使用val令變數為唯讀,但卻還是有例外,使同一個val變數的值改變,所以若要避免發生,必須要main以及函數外,使用const val這樣就能確保不會更改 ### null處理 一般的變數值不能為null,否則會發出例外處理,必須在宣告類別後加上?,代表可令為null 此外處理這種變數呼叫函式,由於無法確定是否為null必須使用下列四種之一 #### 方法一(安全呼叫運算子 **?.**) 若為null會跳過函式 範例(str是null的話會輸出0): ``` fun main(args:Array<String>) { var str = readLine()?.capitalize() var num : Int?=null println(str?.length) } ``` 此外還能使用標準函數let配合使用,當?.前的函數回傳值為null則不會執行let內部 範例(當輸入為空則不進入let): ``` fun main(args:Array<String>) { var str = myFun()?.let { println("str is not null") it.uppercase() } println(str) } fun myFun():String? { var str = readLine() return if (str=="") null else str } ``` #### 方法二(運算子 **!!.**) 強制執行這行指令不管是否為null,因此還是會引發中斷 #### 方法三(直接使用if檢查) #### 方法四(安全合併運算子 ?:) 若左邊不為null,則回傳左邊,否則回傳右邊 範例: ``` fun main(args:Array<String>) { var str1:String?=null var str2:String = "" str2=str1?:"Blue" println(str2) str2=myFun()?:"Blue" println(str2) } fun myFun()="Red" ``` 輸出: ``` Blue Red ``` ## <p id = "if">條件判斷式</p> == 用於比較兩者結構或是值是否相等 === 用於比較兩者參照是否相同(指向記憶體的區塊) **判斷式使用方法:** 單行執行程式時 ``` if () 執行程式 else 執行程式 ``` 通用 ``` if(條件){ 執行程式 } else{ 執行程式 } ``` ``` if(條件){ 執行程式 } else if(){ 執行程式 } else{ } ``` ### 進階用法:條件運算式 這種用法會讓大括弧內只能放值,不能再執行更多程式 ``` 變數 = if (){ 值1 }else if(){ 值2 }else if(){ if(){ 值3 }else if(){ 值4 } }else { 值5 } ``` ``` 變數 = if () 值1 else 值2 ``` ### range運算子 使用..來產生包含一段數字的容器,可以用來判斷變數是否屬於其中,或用於for迴圈,例外可用step決定一次走幾步,預設為1 例如查看變數是否為1~100裡面的整數 ``` if (變數 in 1..100){ 執行程式 } ``` ``` for (i in 1..10 step 2) { println(i); // 1 3 5 7 9 } ``` ### when運算式 用於判斷變數的值來給予特定的程式 判斷變數值來執行單行程式(只能執行單行程式) ``` when (變數) { 值1 -> 執行程式 值2 -> 執行程式 else -> 執行程式 } ``` 判斷變數值來令值 ``` 變數1 = when (變數2) { 值1 -> 值a 值2 -> 值b else -> 值c } ``` 不傳入變數,直接使用條件和執行式 ``` when { 值1 -> 執行程式 值2 -> 執行程式 else -> 執行程式 } ``` ``` when { 值1 -> 值a 值2 -> 值b else -> 值c } ``` ## <p id = "container">泛型容器(集合)</p> 泛型代表著儘管同樣是同一種容器,但內容的元素類型可以是很多種,但一個容器只能裝一種類型的元素 四大容器:**Array List Set Map** **通常宣告型態方式** 容器名<元素類型> ### Array 可以儲存有序的且可重複的元素,但一建立就不可更改Array的長度,但內容依舊可改 #### 宣告的辦法 <>內放入type **arrayOf()** 括號內依序放入元素,會回傳包含那些元素的List **Array<>(size){value or 會回傳值的匿名函數操作}** 建立指定大小的array並全部都令為匿名函數的回傳值 當沒有辦法型態推定時得標明類型 在後面的匿名函數中可以呼叫it,獲取index值,來進行初始化值 **emptyArray<>()** 回傳一個完全空(size=0)的指定型態array **arrayOfNulls<>(size)** 回傳一個的指定大小型態並允許null型別的array **intArrayOf(value1,value2...) or floatArrayOf(value1,value2...) ...** 建立指定內容的array,並且通用於java的原始型別 intArrayOf對應到java原始型別int的array #### 操作使用 []取值 size得知陣列長度 count()得知陣列長度 first()取第一個資料 last()取最後一個資料 sum() average() contentToString()回傳內容形成的字串,用來表達array的樣子,例如回傳出如右的形式[1, 2, 5, -1] copyOf(size)回傳一個從頭開始複製內容並為大小的array,size大於本來array的size,則填入預設值(對int來說是0,string則是null) copyOfRange(startIndex, endUntilIndex)從某index直到另一個index前一個結束,其indx不能超過array的size copyInto()進行複製內容到已存在的array,第一個參數為目標array,第二個參數為目標array要開始寫入的index值,第三個參數為來源array的開始複製的index值,第四個參數為來源array要在哪之前停下 ### List 可以儲存有序的且可重複的元素,一旦建立就不能更改內容和長度,必須使用mutableList **相較於array的記憶體配置是相鄰的,list會動態的分配元素位置,分散儲存而非相鄰位置,適合用於大型資料,因為充分了利用一些小空間** #### 宣告的辦法 **listOf()** 括號內依序放入元素,會回傳包含那些元素的List **List(size){value or 會回傳值的匿名函數操作}** 建立指定大小的list並全部都令為匿名函數的回傳值 當沒有辦法型態推定時得標明類型 在後面的匿名函數中可以呼叫it,獲取index值,來進行特別計算的初始化值 **buildList<>{}** 使用buildList搭配匿名函數來建立List,後面的匿名函數中的this為一個mutableList,可以省略this呼叫mutableList擁有的函數,因此可以用其設定好list內容,最後會回傳List **listOfNotNull()** 括號內依序放入元素,會回傳包含那些元素並去除是null的元素的List **emptyList<>()** 回傳指定型別的空List #### 操作使用 [],get(index)取值 getOrNull(index)取值,index不存在會回傳null size得知陣列長度 sum() average() List的count(){} 無參數,回傳List元素數,或是傳入唯一的匿名參數來自訂計算規則 當使用print來印出list時,會進行是可讀的形式,因為有實作toString(),這點與array不同 withIndex()會回傳一個可迭代的容器,每個元素是index,item形式,搭配解構和for迴圈,可得到list的索引和資料對,效果跟forEachIndexed{}一樣 ``` for ((index,item) in mylist.withIndex()) ``` ### MutableList 可以儲存有序的且可重複的元素,但可更改內容 可使用+=/-=來增加或刪除元素或把某集合元素都加進來或刪除 **mutableListOf()** 括號內依序放入元素,會回傳包含那些元素的MutableList #### 操作使用 add() ### Set 儲存方式是無順序,依靠雜湊值放置,並且以雜湊值辨識出唯一元素,且每個元素都不會重複,雖然是無順序得,但還是可以用index存取,一旦建立就不能更改內容和長度,必須使用mutableSet #### 宣告的辦法 **setOf()** 括號內依序放入元素,會回傳包含那些不重複元素的set **setOfNotNull()** 括號內依序放入元素,會回傳包含那些不重複且非null元素的Set **buildSet<>{}** 使用buildSet搭配匿名函數來建立Set,後面的匿名函數中的this為一個mutableSet,可以省略this呼叫mutableSet擁有的函數,因此可以用其設定好set內容,最後會回傳Set **emptySet<>()** 回傳指定型別的空List #### 操作使用 elementAt(index) size count first() last() ### Map 用以儲存成對的元素,每個value都有對應的key,key不能重複(因為是用Set實作),key的之間類型必須相同,value的之間類別必須相同,key和value類別可不同,一旦建立就不能更改內容和長度,必須使用mutableMap #### 宣告的辦法 **mapOf(),mapOf(key to value,...),map(Pair(key,value),...)** 括號內依序放入元素對,會回傳包含那些元素的Map **buildMap{}** 使用buildMap搭配匿名函數來建立Map,後面的匿名函數中的this為一個mutableMap,可以省略this呼叫mutableMap擁有的函數,因此可以用其設定好Map內容,最後會回傳Map **emptyMap<>()** 回傳指定型別的空Map #### 操作使用 put(key, value) putAll()將容器內所有pair放入map getValue(key) getOrDefault(key, default)當key不存在回傳default值 使用for跌代map時,是以Map.Entry取出,這時可以用.key和.value取,或搭配解構 keys取得map所有key,set型態 values取得map所有value,collection型態 size count() ``` for(pair in map) //map.key map.value for ((key,value) in map) //key value ``` ### 解構Destructuring 可以只用一個運算式,用容器分配多個變數值,並可用_來過濾不要的值 範例: ``` fun main(args: Array<String>) { val list = listOf<Int>(4,2,5,9,3) var(a,_,b,c)=list println("$a $b $c ") } ``` 輸出: ``` 4 5 9 ``` ### 一些共用用元素取index值方法 indexOf()回傳某元素出現的第一個index值,沒有則回傳-1 lastIndexOf回傳某元素出現的最後一個index值,沒有則回傳-1 indexOfFirst()回傳符合匿名函數的第一個index值,沒有則回傳-1 indexOfLast()回傳符合匿名函數的最後一個index值,沒有則回傳-1 ### 一些共用取值方法 get()跟[]同效用 getOrNull()當超出索引值會回傳Null而非拋出例外,可配?:使用,來達到回傳別的預設值 getOrElse()當超出索引值會根據匿名函數進行回傳而非拋出例外 first()取第一個位置或符合匿名函數的元素,若無則拋出例外 firstOrNull() find()取第一個位置或符合匿名函數的元素,若無則回傳Null(find是firstOrNull的封裝,只是為了語意情境而有的) firstNotNullOf()找根據匿名函數設定的變數找第一個不是null的變數,並回傳該值,若無拋出例外 firstNotNullOfOrNull()找根據匿名函數設定的變數找第一個不是null的變數,並回傳該值,若則回傳null ``` class car(val v1:Int?, val v2:String?) { } val a = mutableListOf(car(100, null), car(1, "man"), car(null, "woman")) a.firstNotNullOf { it }.v1 //找list內元素不為null的第一個元素 a.firstNotNullOf { it.v2 } //找list內元素的屬性v2不為null的第一個值 a.first{it.v2 != null}.v2 //與下方同作用,但下方比較簡潔易懂 ``` ``` 100 man man ``` last()取最後一個位置或符合匿名函數的元素,若無則拋出例外 lastOrNull() findLast()取最後一個位置或符合匿名函數的元素,若無則回傳Null random()隨機取一個元素,若容器是空會拋出例外 randomOrNull()隨機取一個元素,若容器是空會回傳Null single()當容器只有一個值時回傳該值,或是當容器只有一個值符合匿名函數時回傳該值,否則拋出例外 singleOrNull()當容器只有一個值時回傳該值,或是當容器只有一個值符合匿名函數時回傳該值,否則回傳Null #### map特化取值方法 getValue()放入key回傳對應值,若key不存在會拋出例外 getOrDefault(key,default)放入key回傳對應值,若key不存在會回傳default getOrPut()放入key回傳對應值,若key不存在會根據匿名函數回傳值,map內放入key和回傳值(這方法只用於mutableMap) #### set特化取值方法,但list等其他容器也可用 elementAt()回傳在某位置的元素,若超過大小會拋出例外 elementAtOrNull()當超出索引值會回傳Null而非拋出例外,可配?:使用,來達到回傳別的預設值 elementAtOrElse()當超出索引值會根據匿名函數進行回傳而非拋出例外 ### 取容器區段系列方法 take(num)從前方開始取num個元素,並回傳新的容器,若超過容器大小就回傳含原容器所有元素的新容器 takeLast(num)從後方開始取num個元素,並回傳新的容器,若超過容器大小就回傳含原容器所有元素的新容器 takeWhile{}從前方開始取符合匿名函數的元素直到遇到不符合就停下,並回傳新的容器 takeLastWhile{}從後方開始取符合匿名函數的元素直到遇到不符合就停下,並回傳新的容器 ``` class Car(val v1:Int, val v2:String) { override fun toString(): String { return "$v1 : $v2" } } val a = mutableListOf(Car(100, "Peter"), Car(1, "man"), Car(200, "woman"), Car(42, "Ken"), Car(56, "Oda")) a.take(2) a.takeLast(3) a.takeWhile{ it.v1 < 150 } a.takeLastWhile { it.v1 < 150 } ``` ``` [100 : Peter, 1 : man] [200 : woman, 42 : Ken, 56 : Oda] [100 : Peter, 1 : man] [42 : Ken, 56 : Oda] ``` drop() dropLast() dropWhile() dropLastWhile() 以上方法跟take一樣呼叫,但從取變成刪去 ``` a.drop(2) a.dropLast(3) a.dropWhile{ it.v1 < 150 } a.dropLastWhile { it.v1 < 150 } ``` ``` [200 : woman, 42 : Ken, 56 : Oda] [100 : Peter, 1 : man] [200 : woman, 42 : Ken, 56 : Oda] [100 : Peter, 1 : man, 200 : woman] ``` slice()參數可放入range物件或是包含特定index的可跌代的容器物件來進行取特定位置物件並回傳新容器,只有list有這個函數,array要用arraySlice() ``` a.slice(1..3) a.slice(1..3 step 2) a.slice(listOf(0,3,2,3)) ``` ``` [1 : man, 200 : woman, 42 : Ken] [1 : man, 42 : Ken] [100 : Peter, 42 : Ken, 200 : woman, 42 : Ken] ``` chunked(size){}把容器切成固定大小的子容器,最後一個子容器可能會不足大小,最後都放進新容器回傳,匿名函數做而額外操作(it會代表經過切割的子容器),並影響最後回傳的內容 ``` a.chunked(2) a.chunked(2){ subList -> subList.sumOf {it.v1}} ``` ``` [[100 : Peter, 1 : man], [200 : woman, 42 : Ken], [56 : Oda]] [101, 242, 56] ``` windowed()把容器用如Tcp slideWindow的方式切割,最後一個子容器可能會不足大小,最後都放進新容器回傳 第一個參數為window size 第二個參數為window每次移動多少,預設1 第三個參數為為布林值決定要不要留不完整大小的子容器,預設false 最後參數匿名函數做額外操作(it會代表經過切割的子容器),並影響最後回傳的內容 ``` val a = listOf(1,2,3,4,5) a.windowed(2) a.windowed(2,2, true) a.windowed(2,2, true){it.sum()} ``` ``` [[1, 2], [2, 3], [3, 4], [4, 5]] [[1, 2], [3, 4], [5]] [3, 7, 5] ``` zipWithNext()把容器相鄰元素變成pair放入新容器,可傳入匿名函數做額外操作 ``` val a = listOf(1,2,3,4,5) a.zipWithNext() a.zipWithNext(){a, b -> a*b} ``` ``` [(1, 2), (2, 3), (3, 4), (4, 5)] [2, 6, 12, 20] ``` ### binarySearch系列方法 以下方法使用前都必須經過排序 binarySearch()第一個參數為要找得目標元素,第二個參數為比較的基準,第三個為開始的index(預設0),第四個參數為到哪個index前停下(預設容器大小),最後回傳index binarySearchBy()第一個參數為要找得目標元素值,第二個參數為開始的index(預設0),第三個為到哪個index前停下(預設容器大小),第四個參數為匿名函數,設定比較的基準,最後回傳index ``` class Car(val v1:Int, val v2:String) { override fun toString(): String { return "$v1 : $v2" } } val a = mutableListOf(Car(100, "Peter"), Car(1, "man"), Car(200, "woman"), Car(42, "Ken")) a.sortedBy { it.v1 }.apply { println(this) println(this.binarySearch(Car(100,"Ray"), compareBy{it.v1})) println(this.binarySearchBy(100){it.v1}) //與上方相同功用 } ``` ``` [1 : man, 42 : Ken, 100 : Peter, 200 : woman] 2 2 ``` ### 排序系列方法 以下方法都回傳新的容器 sorted()根據由小排到大排序(負數至正數、A至Z至a至z) sortedDescending()根據由大排到小排序 sortedBy(){}根據傳入匿名函數決定用甚麼由小排到大排序 sortedByDescending(){}根據傳入匿名函數決定用甚麼由大排到小排序 ``` val a = listOf(-10,6,-2,1,3,-7,5) a.sorted() a.sortedDescending() a.sortedBy { it.absoluteValue } a.sortedByDescending { it.absoluteValue } ``` ``` [-10, -7, -2, 1, 3, 5, 6] [6, 5, 3, 1, -2, -7, -10] [1, -2, 3, 5, 6, -7, -10] [-10, -7, 6, 5, 3, -2, 1] ``` sortedWith()可以支援comparator自訂排序方式,或用compareBy{}來達成 ``` val comparator = Comparator{p1:List<Int> , p2:List<Int> -> p2.sum()-p1.sum() } a.windowed(2,2,true) a.windowed(2,2,true).sortedWith(comparator) a.windowed(2,2,true).sortedWith(compareByDescending{ it.sum() }) a.windowed(2,2,true).sortedWith(compareBy{ it.sum() }) a.windowed(2,2,true).sortedWith(compareBy({it.sum()}, { it[1] })) ``` ``` [[-10, 6], [-2, 1], [3, -7], [5]] // origin [[5], [-2, 1], [-10, 6], [3, -7]] [[5], [-2, 1], [-10, 6], [3, -7]] [[-10, 6], [3, -7], [-2, 1], [5]] [[3, -7], [-10, 6], [-2, 1], [5]] ```` comparator的匿名函數傳入兩參數,必是回傳整數 正代表數字大、排序後 負代表數字小、排序前 0等於數字相等 reversed()完全將容器排序反過來 shuffled()將容器隨機排序 以下方法則都會改變原容器 sort() sortDescending() sortBy(){} sortByDescending(){} reverse() shuffle() ### 檢查容器相關方法 isEmpty()看容器是否為空,true代表為空(內部實作使用length檢查) isNotEmpty()看容器是否為空,true代表非空 isNullOrEmpty()看容器是否為空或null,true代表是空或是null(不用?.呼叫,否則會回傳null) ``` val a : MutableList<Int>? = null a.isNullOrEmpty() // true a?.isNullOrEmpty() // null ``` any(){}檢查容器內是否有至少一個元素符合條件,若沒有設定條件則作用如同isNotEmpty() none(){}檢查容器內是否沒有元素都不符合條件,則回傳true,若沒有設定條件則作用如同isEmpty() all(){}檢查容器內是否全部元素都符合條件,則回傳true,一定要設定條件,若使用空的集合則會永遠回傳true ``` val a : MutableList<Int> = mutableListOf(1,2,10) a.any() a.none() a.any{it > 5} a.none { it > 5 } a.none { it > 11 } a.all{ it < 5} ``` ``` true false true false true false ``` contains()檢查容器內是否有某元素,使用在map上時相當於containsKey() containsAll()檢查容器內是否有指定的所有元素 containsKey()檢查map容器內是否有某個key containsValue()檢查map容器內是否有某個value ### 操作容器相關方法 基本上都會改變原容器內容 add(element) add(index,element) addAll(cotainer) addAll(index,cotainer)將容器內所有元素加到容器最後面或指定index,並回傳true(只有add會回傳,addAll不回傳東西 ),index值必須大於等於0小於等於容器大小 plus()將元素(容器內所有)加到容器最後面並回傳成新容器,不會改變原容器內容,可用+代替(運算子多載) plusAssign()將元素(容器內所有)加到容器最後面,不回傳任何東西,會改變原容器內容,可用+=代替(運算子多載),作用如同add plusElements()將指定容器整個加到容器內,不會對指定容器展開內部,並回傳成新容器 remove(element)把容器內第一個符合指定元素的元素移除,成功移除會回傳true,若找不到就回傳false removeAt(index)把容器內指定index位置的元素移除,並回傳被移除的元素,index值必須大於等於0小於等於容器大小 removeAll(cotainer) removeAll{}把容器內有出現在指定容器內的容器都移除或是利用匿名函數設定條件 removeFrist()把容器第一個元素移除並回傳該元素,但容器不能是空的否則會例外處理,也可使用removeFirstOrNull()來使其在容器空時回傳null removeLast() removeLastOrNull()用法同上,但是是移除最後一位 removeIf{}作用跟removeAll{}一樣 minus()若傳入元素則刪除第一個符合的元素,若傳入容器則刪除全部符合的元素,最後回傳成新容器,不會改變原容器內容,可用-代替(運算子多載) minusAssign()若傳入元素則刪除第一個符合的元素,若傳入容器則刪除全部符合的元素,最後會改變原容器,不回傳任何東西,可用-=代替(運算子多載) minusElements()將指定容器整個從容器內移除,不會對指定容器展開內部,並回傳成新容器 retainAll(cotainer) retainAll(){} 只保留指定容器內的元素或是利用匿名函數設定條件 clear()清空容器 set(index, value)更新指定位置的值,可用[]=代替(運算子多載),會回傳值原本在這個位置的值 replaceAll(){}對每個位置都進行匿名函數的操作後並取代原值 fill(value)對每個位置都指定為相同值,若容器是array則可以再加上兩個參數在後面來限制範圍,分別為開始的index以及哪一個index前 #### Map特化操作容器相關方法 set(key, value)一樣更新對應的位置的值,不存在key則新增這對key-value,但跟其他容器不同,不會有任何回傳值 put(key, value)跟set一樣,但會有回傳值,會回傳值原本key對應的值,若無此key則回傳null putAll(cotainer)將指定容器內所有pair加入容器,不會有任何回傳值 ### 容器分群相關方法 groupBy({},{})根據第一個傳入匿名函數決定分群的基準,並依其值為key,元素分類相同值對應的list內,第二個匿名函數可以讓元素的最終值進行統一操作或決定要怎樣形式,最後回傳(不可變)map容器 groupByTo(dest, {}, {})跟groupBy用法一樣但多一個參數可指定結果要加入哪容器,該容器一定是可變的 groupingBy{}根據第一個傳入匿名函數決定分群的基準,回傳group類別,搭配一些group類別的函數達到方便指定效果(eachCount,fold,reduce,aggregate) 範例: ``` class Car(val v1:Int, val v2:String) { override fun toString(): String { return "$v1 : $v2" } } ``` ``` val a = listOf(Car(100, "Peter"), Car(180, "man"), Car(200, "woman"), Car(100, "Ken")) a.groupBy ({it.v1>150},{it.v2}) val b = listOf(13,2,10,9,4,7,1) b.groupBy({ it%2 },{it*10}) val c = mutableMapOf<Int, MutableList<String>>(300 to mutableListOf("Jim", "Zen")) a.groupByTo(c,{it.v1},{it.v2}) a.groupingBy { it.v1 }.eachCount() a.groupBy{ it.v1 }.map { it.key to it.value.count() }.toMap() ``` ``` {false=[Peter, Ken], true=[man, woman]} {1=[130, 90, 70, 10], 0=[20, 100, 40]} {300=[Jim, Zen], 100=[Peter, Ken], 180=[man], 200=[woman]} {100=2, 180=1, 200=1} {100=2, 180=1, 200=1} ``` partition{}匿名函數放入回傳為布林值的判斷條件,最後回傳一個新的pair,前面放符合條件的元素list,後面則放不符合條件的元素list ``` val a = listOf(Car(100, "Peter"), Car(180, "man"), Car(200, "woman"), Car(100, "Ken")) val (g,h)=a.partition { it.v1 > 150 } a.partition { it.v1 > 150 }.first g h ``` ``` [180 : man, 200 : woman] [180 : man, 200 : woman] [100 : Peter, 100 : Ken] ``` distinct()將容器內重複值元素去除,回傳新的容器 distinctBy{}將容器內根據條件重複值的元素去除,保留先出現的元素,回傳新的容器 ``` val d = listOf(-2,5,2,8,5,8,-8,10) d.distinct() d.distinctBy { it.absoluteValue } ``` ``` [-2, 5, 2, 8, -8, 10] [-2, 5, 8, 10] ``` 以下函數都有+To版本,可將結果附加至指定容器,其第一個參數為指定容器 filter{}根據匿名函數的條件篩選,合條件的元素回傳為新的容器,當對map使用時會得到的it是Map.Entey,透過.key或.value就可操作 filterNot{}根據匿名函數的條件篩選,不合條件的元素回傳為新的容器 filterIndexed{}匿名函數除了傳入元素也傳入了index值(先index後元素),合條件的元素回傳為新的容器 filterNotNull()將容器不為null的元素留下,並且相較於原容器型態會變成不可空filterIsInstance\<type\>()將容器為特定類別的元素留下,並且相較於原容器型態會變成該類別 ``` open class Car(val v1:Int, val v2:String) { override fun toString(): String { return "$v1 : $v2" } } class SportCar(private val v4:Int, private val v5:String, var v3:Double) : Car(v4, v5) { override fun toString(): String { return "${super.toString()} : $v3" } } val a = listOf(Car(100, "Peter"), Car(180, "man"), Car(200, "woman"), Car(100, "Ken")) a.filter { it.v1>150 } a.filterNot { it.v1>150 } a.filterIndexed { index, car -> index>1 && car.v1 > 150 } val b = listOf(null, "abc", "de") b.filterNotNull() b.filter { it != null } val c = listOf(10, 'c', "abc", Car(120, "Lan"), SportCar(500, "Bob", 4.24)) c.filterIsInstance<Car>() ``` filterKeys()為map特化的函數,可直接以it為key來做篩選 filterValues()為map特化的函數,可直接以it為value來做篩選 ### 取集合相關方法 union(container)跟指定容器做聯集,並回傳新容器 subtract(container)跟指定容器做差集,並回傳新容器 intersect(container)跟指定容器做交集,並回傳新容器 ### associate系列方法 list搜尋元素時,會將全部都看一遍,導致效能比較差,透過associate轉成map,可以更有效率 以下轉換,若有key相同時,後面會蓋掉前面(造成前後容器尺寸不同) 以下轉換,都回傳不可變map,加上To,則可把結果加到可變容器,這樣第一參數則是指定容器 associate{}根據匿名函數的回傳pair形式,前為key後為對應值,匿名函數必須要回傳pair,回傳一個map型態新容器,效果等同map{ pair }.toMap() associateBy{}如果只傳一個匿名函數,則限定對應值一定是原物件,所以匿名函數只需要回傳key,回傳一個map型態新容器,若傳兩個,則可以繼續更改要對應的值 associateWith{}限定key一定是原物件,所以匿名函數只需要回傳value,回傳一個map型態新容器 ``` val a = listOf(Car(0, "Peter"),Car(1, "Caron"),Car(2, "Malone")) a.associate { it.v1 to it.v2 } a.map { it.v1 to it.v2 }.toMap() a.associateBy { it.v1 } a.associateBy ({ it.v1 },{ it.v2 }) a.associateWith { it.v1 } ``` ``` {0=Peter, 1=Caron, 2=Malone} {0=Peter, 1=Caron, 2=Malone} {0=0 : Peter, 1=1 : Caron, 2=2 : Malone} {0=Peter, 1=Caron, 2=Malone} {0 : Peter=0, 1 : Caron=1, 2 : Malone=2} ``` ### Scan系列方法 scan(){}會傳入初始值,並且擁有一個累加器(暫存器),每次照順序將累加容器內元素,每次結果都存在list和累加,最後回傳這個list,而list第一個值是初始值,匿名函數有兩個參數,累加器和容器內元素 scanIndexed(){}跟上面只差在匿名函數多一個參數在最前面是容器元素的index ``` val a = listOf(100,1000,2000) a.scan(1){acc, num -> println("acc: $acc num: $num") acc+num} a.scanIndexed(1){index: Int, acc: Int, num: Int -> if (index % 2 == 0) acc+num else acc-num } ``` ``` acc: 1 num: 100 acc: 101 num: 1000 acc: 1101 num: 2000 ``` ``` [1, 101, 1101, 3101] [1, 101, -899, 1101] ``` ### 計算統計相關方法 count(){}回傳容器內元素數量,實作其實是回傳容器.size,所以兩者相同,可傳入匿名函數設定數量計算條件 sum()回傳所有元素的總和,元素必須是可加總 sumOf{}適用於元素不是單純的數值類別時,會將所有元素做轉換,再以此加總,必須轉換到可加總的資料型態,回傳總和,如同map{}後做sum() average()回傳所有元素的平均值 以下max都可改名成min使用 maxOrNull()回傳所有元素的最大值,當容器是空會回傳null maxByOrNull{}適用於元素不是單純的數值類別時,可用匿名函數選擇要比較的屬性,會回傳擁有最大值的元素,如果有兩個以上元素都有最大值會回傳先出現的 maxOfOrNull{}與maxByOrNull{}差在不是回傳元素,而是回傳最大值 maxOf{}與上個用法一樣,但當容器空會發生例外 maxWithOrNull()支援用Comparator自訂比較邏輯,參數為Comparator,回傳擁有最大值的元素 maxOfWith()適合在資料複雜的情況使用,第一參數為Comparator,第二參數為比較對象,回傳擁有最大值的元素 maxOfWithOrNull()與上個比較,當空會回傳null ### join子串相關方法 joinToString()有六個參數每個都是有預設值,讓容器內容可以自訂豐富的方式變成字串表達 第一個separator,每個元素之間要填充的字串,預設為", " 第二個prefix,字串最前面要填充的字串,預設為沒有 第三個postfix,字串最後面要填充的字串,預設為沒有 第四個limit,字串要放入的元素數量,從頭開始放,預設為全部數量 第五個truncated,字串元素若超過limit,則要放什麼字串表示,預設為... 第六個transform,為設定可以如何轉換元素再放入字串的匿名函數,預設為不轉換 joinTo()跟上方用途一樣,但增加一個參數在最前面,可以指定一個Appendab的物件(如StringBuffer),當作轉換後的結果的附加位置 ### zip zip(){}合併兩個容器,並把對應的位置合併成一個pair型態,也接受匿名函數做額外處理(這樣節省呼叫了一個map) 和flatmap不同(把同一容器內的不同容器合併) ``` fun main(args: Array<String>) { val list1 = listOf(1, 2, 3) val list2 = listOf(10, 20, 30) val combineList = list1.zip(list2) println(combineList) println(combineList.toMap()) println(list1.zip(list2) { first, second -> first + second }) } ``` 輸出: ``` [(1, 10), (2, 20), (3, 30)] {1=10, 2=20, 3=30} [11, 22, 33] ``` unzip()將容器內的pair分割成兩個容器,並裝在一個pair 範例: ``` val a = listOf(1 to "a", 2 to 'c', 3 to 'z') a.unzip() ``` 輸出: ``` ([1, 2, 3], [a, c, z]) ``` ### reduce fold累積計算系列方法 reduce{}用匿名函數決定累加方法,暫存器的初始值為第一個元素,匿名函數第一個參數是暫存器,第二個是容器元素,元素由index小至大傳入,若容器為空會出先例外 reduceIndexed{}匿名函數多一個傳入參數在最前面是元素對應的index reduceOrNull{}若容器為空會回傳Null ``` val list1 = mutableListOf(1) for(i in (1..8)) { val temp = list1.reduce{accumulator,number-> accumulator+number} list1.add(temp) } list1 list1.reduce { accumulator,number-> accumulator+number} ``` ``` [1, 1, 2, 4, 8, 16, 32, 64, 128] 256 ``` reduceRight{} 元素由index大至小傳入 reduceRightIndexed{} 元素由index大至小傳入,但在匿名函數中,傳入參數位置不同於先前,順序為index 元素 暫存器 fold(){}傳入一個初始累加值,並根據初始值以及容器元素值更新累加值最後回傳累加值,匿名函數第一個參數是暫存器,第二個是容器元素,元素由index小至大傳入 foldIndexed(){}匿名函數多一個傳入參數在最前面是元素對應的index foldRight(){}元素由index大至小傳入 foldRightIndexed(){}元素由index大至小傳入,但在匿名函數中,傳入參數位置不同於先前,順序為index 元素 暫存器 ``` val list1 = mutableListOf(1) for(i in (1..8)) { val temp = list1.fold(1){accumulator,number-> accumulator+number} list1.add(temp) } list1 ``` ``` [1, 2, 4, 8, 16, 32, 64, 128, 256] 512 ``` ### 容器轉換型態系列方法 toIntArray() toBooleanArray() toCharArray() toFloatArray() toDoubleArray()...都是將容器轉成裝原始型別的容器,效能比較好一點 toTypedArray()會將容器自動轉成相同型別的array toMap() toSet() toList()...轉成其他容器 toMutableList()...轉成其他可變容器 toCollection()可把容器轉型後,將內容加進某可變容器,參數是目的地,但目的地內的元素型態必須跟原容器兼容 toSortedMap() toSortedSet()可傳入comparator自訂排序,否則預設為小到大 toPair()為Map.Entry型別用的函數,可將其轉成pair toProperties()為Map<String, String>型別用的函數,可轉成java的Properties類別 ### 迭代 大多容器都支援迭代 #### 一般for迴圈 for 變數 in 容器 範例: ``` fun main(args: Array<String>) { val list = listOf<Int>(4,2,5,9,3) for (item in list){ println(item) } } ``` 輸出: ``` 4 2 5 9 3 ``` 範例: ``` val myList = listOf(1,6,3,3,89,40) for (index in myList.indices) println("[$index] = ${myList[index]}") ``` 輸出: ``` [0] = 1 [1] = 6 [2] = 3 [3] = 3 [4] = 89 [5] = 40 ``` #### iterator配while hasNext()會檢查是否有下一物件 next()會回傳當前指向物件,並指向下一物件 ``` val a = listOf(1, 2, 5) val iterator = a.iterator() while (iterator.hasNext()) { println(iterator.next()) } ``` ``` 1 2 5 ``` #### 直接呼叫foreach配匿名函數 用容器呼叫foreach,並可以在foreach內加入判斷 範例: ``` fun main(args: Array<String>) { val list = listOf<Int>(4,2,5,9,3) list.forEach{if (it>=5) println(it)} } ``` 輸出: ``` 5 9 ``` #### 直接呼叫forEachIndexed配匿名函數 跟foreach的差別在於,匿名函數的參數多了每個元素的index值 範例: ``` fun main(args: Array<String>) { val list = listOf<Int>(4,2,5,9,3) list.forEachIndexed(){index, item -> println("$index : $item ") } } ``` 輸出: ``` 0 : 4 1 : 2 2 : 5 3 : 9 4 : 3 ``` #### iterator()搭配迴圈 可跌代容器都有.iterator()可呼叫,會回傳一個單向迭代容器(指到最後就無法使用,必須重新建立新的),for迴圈會檢查該物件是否有實作interface Iterator或擴充 .next()會回傳當前值再指向下一個值 ``` val a = listOf(1, 2, 5) val iterator = a.iterator() while (iterator.hasNext()) { println(iterator.next()) } ``` ``` 1 2 5 ``` #### 更多Iterator ListIterator: list特化的跌代器,除了可以向後還有向前功能(.previous(),.hasPrevious...) MutableIterator: 增加了remove()方法 MutableListIterator: 增加了add() set()方法 #### val 的不可變用於list val只能保證list1代表的容器是同樣的位置 範例: ``` fun main(args: Array<String>) { val list1 = listOf(mutableListOf(1,2,3)) list1[0].add(4) println(list1) } ``` 輸出: ``` [[1, 2, 3, 4]] ``` ### 可迭代容器可搭配函數 轉換 過濾 合併 ### 轉換 map 和 flatmap #### map 以下函數都有To版本可用,第一參數可加一個可變容器作為目的地,將結果附加至該容器 map{}尋訪容器,並針對每個元素根據傳入的匿名函數做轉換,並回傳一個新的集合 mapIndexed{}增加一個index作為第一傳入的參數,其餘跟map{}相同 mapNotNull{}除了跟map{}一樣還可以避免經過轉換後變成null的元素加到新容器 mapIndexedNotNull{}同上只是有index作為第一傳入的參數 #### Map類別特化map mapKeys{}用來將keys值轉化,回傳新的Map mapValues{}用來將Values值轉化,回傳新的Map 範例: ``` val record = mapOf(1 to "Kimmy", 2 to "Ova" , 5 to "Unabated") record.mapKeys { it.value.length } record.mapKeys { (_,name)-> name.length } record.mapValues { it.value+it.key.toString() } ``` 輸出: ``` {5=Kimmy, 3=Ova, 8=Unabated} {5=Kimmy, 3=Ova, 8=Unabated} {1=Kimmy1, 2=Ova2, 5=Unabated5} ``` #### flatten flatmap flatten()將容器中的容器的元素都展開合併,但只會找往內找一層 使用時,每個元素必須是可被展開的容器才能 ``` val a = listOf(listOf(1,23,4),listOf(2,5,40),listOf(100,54, listOf(1,2,3))) a.flatten() val b = listOf(listOf(listOf(1,2,3),listOf(1,2,3)),listOf(listOf(1,2,3),listOf(1,2,3)),listOf(listOf(1,2,3), listOf(1,2,3))) b.flatten() b.flatten().flatten() ``` ``` [1, 23, 4, 2, 5, 40, 100, 54, [1, 2, 3]] [[1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3]] [1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3] ``` flatmap{}尋訪容器中的容器,並針對每個容器根據傳入的匿名函數做轉換,匿名函數最後需要回傳的是可跌逮代的容器,並回傳一個新的集合包含所有內部容器的元素,效果等同map{}後flatten() 類似將容器中的容器的元素都合併,但只會找往內找一層 flatmapTo{}第一個參數則可以傳結果到指定可變容器 範例: ``` fun main(args: Array<String>) { val list1 = listOf(1,2,3) val list2 = listOf(4,5,6) val list3 = listOf(list1,list2) val newList1 = list1.map { it*10 } val combineList = listOf(list1,list2) val combineList2 = combineList.flatMap{ println("In flatMapping1, $it") it.sortedDescending()} val combineList3 = listOf(list2,list3).flatMap { println("In flatMapping2, $it") it } println("new list1: $newList1") println("combineList2: $combineList2") println("combineList3: $combineList3") } ``` 輸出: ``` In flatMapping1, [1, 2, 3] In flatMapping1, [4, 5, 6] In flatMapping2, [4, 5, 6] In flatMapping2, [[1, 2, 3], [4, 5, 6]] new list1: [10, 20, 30] combineList2: [3, 2, 1, 6, 5, 4] combineList3: [4, 5, 6, [1, 2, 3], [4, 5, 6]] ``` ### 範例-找質數 none含函數:傳入判斷式函數,檢查集合內元素若都沒有符合true的元素則回傳true 範例 ``` fun main(args: Array<String>) { val numList = listOf(2,3,6,9,11,13,15) val primeList = numList.filter { number ->(2 until number).map { number % it }.none{it == 0} } // if each element is not 0 return true means number is prime println(primeList) } ``` 輸出: ``` [2, 3, 11, 13] ``` ### 及早容器 惰性容器 eager collection及早容器(集合)為list map set lazy collection惰性容器(集合)為seqenece #### 惰性容器 不會以索引值排序,不紀錄元素數量,可以定義一個跌代器函數 對於非常大的元素數量時有比較好的效能 ### Sequence 為一種惰性容器,即不會事先花費空間,只有直到要取出使用時才會真正的去生成數值並占用空間,因此無法用索引值取值,比起容器更像是生成器 使用條件有兩個,一是必須要有明確的停止條件(不論是指定取出數量還是用條件的方式),二是要搭配型轉使用才能讓Sequence真正開始生成內部元素 若對沒有限制停止條件的sequenc進行型轉,他會不斷生成造成無限迴圈 #### generateSequence(seed){}產生一個容器 第一個值為seed,之後匿名函數的it為前一數,用於設定當前位置值,並取回傳值為當前位置的值 take()可以做為限制條件,型轉則會讓它真正執行生成 ``` var a = generateSequence(1) { it*2 } //1 2 4 8 16 32... var b = generateSequence(1) { it*2 if (it<200) it*2 else null} println(a.take(5).toList()) println(b.toList()) ``` ``` [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] [1, 2, 4, 8, 16, 32, 64, 128, 256] ``` #### 搭配其他容器在部分情況下能增加處理效能 sequence的執行方式會依照單元素進行執行所有函數,而不會讓所有元素都做完同一函數才做下個函數 ``` var a = "The quick brown fox jumps over the lazy dog ".split(" ") println("Normal list test:") a.filter {print("$it ") it.length >3 }.take(2) println("\nlist toSequence test:") a.asSequence().filter {print("$it ") it.length >3 }.take(2).toList()\ ``` ``` Normal list test: The quick brown fox jumps over the lazy dog list toSequence test: The quick brown ``` #### 不適合的使用狀況 1.最後搭配toList() toSet()可能會比較耗效能,因為sequence是在不知道全部長度的狀態下進行 2.不適合sorted() distinct()需要紀錄狀態的函數,反而更耗費效能,因為需要先轉換再轉換回sequence ## <p id = "function">函數</p> **標準宣告(預設值可省略):** **可見性修飾符 fun 函數名稱 (參數名:型態=預設值,...):回傳值型態** { ... return 回傳值或變數 } **具名的函數呼叫** 當函數呼叫時,如果沒打上參數名稱,則會以參數定義順序傳入,但若有打上參數名稱則不用照順序,傳入的參數是唯讀變數 使用方式: 函數名(參數名=值,參數名=值,...) **更方便的宣告方法(適用於單運算式):** **可見性修飾符 fun 函數名稱 (參數名:型態,...):回傳值型態=運算式或回傳值的條件式** 此時回傳值型態可省略 範例: ``` fun main(args:Array<String>) { var a = 10 println("${myFun1(a)}") println("${myFun2(a)}") println("${myFun3(a)}") println("${myFun4(num=a)}") } private fun getTrue(): Boolean { return true } private fun myFun1(num:Int):Int=num*2 private fun myFun2(num:Int)=if (num==20)30 else 40 private fun myFun3(num:Int)=when (num) { 5->"a" 20->60 else -> myFun2(num) } private fun myFun4(num:Int)=println(num) ``` output: ``` 20 40 40 10 kotlin.Unit ``` ### Unit 若函數沒有實際要回傳的值,則回傳Unit型態 ### Nothing 當使用了TODO()函式,其回傳的型態即是Nothing,跟Unit一樣都是回傳沒意義的東西,但Notihng特別表示了程式無法執行完成(拋出異常...) ### infix 若在成員函數或泛型函數宣告前加入infix代表這個函數呼叫時可以省略.() 如同建立pair時用的to,並非關鍵字而是使用了infix宣告to函數 不過有三個限制: * 函數是成員函數或擴充函數 * 函數只有一參數 * 參數類別不能為vararg且無預設值 ``` class Car(val v1:Int, val v2:String) { override fun toString(): String { return "$v1 : $v2" } infix fun add(num:Int):Int { return v1+num } } var a = Car(10,"Man") a add 45 ``` ``` 55 ``` ## 匿名函數 跟一般函數差在沒有名稱,以及通常把最後一行的值作為retrun的值,而不用真正使用return 注意 下面範例的(String,Int)->String用來表示變數匿名函數回傳的是string,若無參數可省略,否則要在匿名函數內宣告參數 範例: ``` fun main(args:Array<String>) { val a:(String,Int)->String = {Temp,num-> "4234$Temp$num"} val b = {Temp:String,num:Int-> "4234$Temp$num"} println(a) println(a("B",10)) println(b("A",20)) println({ val str = "apple" "4234$str"}()) } ``` 輸出: ``` Function2<java.lang.String, java.lang.Integer, java.lang.String> 4234B10 4234A20 4234apple ``` 注意 此外也可當作回傳值或參數傳入(在匿名函數內可使用外部區域變數,但會導致其生命週期變成gobal) **注意若回傳的是匿名函數的物件時候,呼叫要加上()否則無法正常執行,只是物件本身** 範例: ``` fun main(args:Array<String>) { val result = myFun(){ val temp = (1..100).shuffled().last() println("randomNum:$temp") temp} println(result()) println(result()) } fun myFun(randomNum:()->Int):()->Int { var num=5 return { println(num) num+=randomNum() num } } ``` 輸出: ``` 5 randomNum:4 9 9 randomNum:81 90 ``` ### it 當匿名函數只有一個參數,可以用it來呼叫 範例: ``` fun main(args:Array<String>) { val num = "4234".count{ it == '4' } val a:(String)->String = { "4234$it"} println(a("B")) println(num) } ``` 輸出: ``` 4234B 2 ``` ### inline 當某個宣告標準標準函數傳入參數有匿名函數,可在fun前加入inline,可以可以節省記憶體,避免jvm當作物件處理,加入後編譯器會將匿名函數copy到該位置以節省記憶體 ### 關鍵字 vararg 當在參數列使用會自動將參數變成array傳入 範例: ``` fun main(args: Array<String>) { myFun(33,2, obj = 10) myFun(40,obj = 34) } fun myFun(vararg item:Int,obj:Int) { println(item[0]) } ``` 輸出: ``` 33 40 ``` ## <p id = "standardfunction">標準函數</p> 一些支援lambda的通用工具類函數,本質上都是擴充函數,通常搭配一個接收者(Context object)呼叫使用 **標準函數比較表格** | 函數 | 是否傳入接收者到匿名函數 | 返回 | 使用情境 | | ----------------- |:------------------------:| ---------------|------------------------| | apply | 否 用this | 接收者物件 |設定物件屬性 | | also | 是 用it | 接收者物件 |完成後要串接其他方法 | | let | 是 用it | lambda結果 |對物件執行一段程式 | | run | 否 用this | lambda結果 |執行需要表達式的程式碼 | | with | 否 用this | lambda結果 |在一物件呼叫一系列方法 | | takeIf/takeUnless | 是 用it | 可空的接收者物件 | | ### function reference 當使用這些標準函數,當只要對某傳入參數只有一個並要傳入this或it的函數,可以用::函數名來代替傳統呼叫 ``` fun main() { var a = 10 println(a.apply(::f1)) println(a.also(::f1)) println(a.let(::f1)) println(a.run(::f1)) } fun f1(num:Int):Int { return num*10 } ``` ``` 10 10 100 100 ``` ### apply 可視為一種配置函數,不會傳任何引數進匿名函數(所以用it也沒用),但可用this使用原物件,會把結果返回改接收者(即本例的File("menu-file.txt")) 範例: ``` fun main(args: Array<String>) { val menuFile = File("menu-file.txt") menuFile.setReadable(true) menuFile.setWritable(true) menuFile.setExecutable(false) } ``` 上述可以用apply改成 ``` fun main(args: Array<String>) { val menuFile = File("menu-file.txt").apply { setReadable(true) setWritable(true) setExecutable(false) } } ``` ### let 可使某變數作用於匿名函數,接收者為其唯一引數,所以可以用it 範例1: ``` fun main(args: Array<String>) { val a=listOf(1,2,3).first() val b=a*a } ``` 上述可以使用let改成 ``` fun main(args: Array<String>) { val b=listOf(10,20,30).first().let { it*it } } ``` 搭配安全呼叫使用?.let,當值為空時,會不執行let後的匿名函數 範例2: ``` fun test(str : String?):String{ return str?.let { "The string is $it" }?:"The string is null!!!" } fun main() { println(test(null)) println(test("mouse")) } ``` 輸出: ``` The string is null!!! The string is mouse ``` ### run 跟apply差不多,但不是返回接收者,而是lambda結果或是函數結果 範例1: ``` fun main(args: Array<String>) { val boolResult = File("menu.txt").run { setReadable(true) setWritable(true) } println(boolResult) } ``` 輸出結果即為menufile這個函數並存下了setWritable(true)回傳的boolean結果 另一種用法 範例2: ``` fun main(args: Array<String>) { var num = 3 num.run(::f1).run(::f2).run(::println) num = 12 num.run(::f1).run(::f2).run(::println) } fun f1(num:Int) = num>10 fun f2(flag:Boolean):String { return if (flag) "num>10!!" else "num<=10!!!" } ``` 輸出: ``` num<=10!!! num>10!! ``` ### with 屬於run的變形,功能行為都一樣,差在呼叫方式 範例: ``` fun main(args: Array<String>) { val menuFile = with(File("menu.txt")){ setReadable(true) setWritable(true) } println(menuFile) } ``` ### also 跟let只差在回傳的是接收者物件 範例: ``` fun main(args: Array<String>) { var content : List<String> val menuFile = File("menu.txt").also { it.setReadable(true) it.setWritable(true) content = it.readLines() //讀取所有檔案內行,每一行都放進list的一個元素 } println(content) } ``` ### takeIf 功能類似if且更簡化,傳入接收者為唯一變數,使用條件式的匿名函數,根據匿名函數結果來決定回傳的值,True則回傳接收者物件,false則回傳null 範例: ``` fun main(args: Array<String>) { var num :Int = 10 num.takeIf { it>5 }?.run(::println)?: println("num is too small!!!") num = 1 num.takeIf { it>5 }?.run(::println)?: println("num is too small!!!") } ``` 輸出: ``` 10 num is too small!!! ``` ### takeUnless 跟takeIf功能一樣,但邏輯不一樣,當匿名函數結果為false才回傳接收者物件 範例: ``` fun main(args: Array<String>) { var num :Int = 10 num.takeUnless { it<=5 }?.run(::println)?: println("num is too small!!!") num = 1 num.takeUnless { it<=5 }?.run(::println)?: println("num is too small!!!") } ``` 輸出: ``` 10 num is too small!!! ``` ## <p id = "exception">例外處理</p> 使用throw IllegalStateException()拋出例外,並會中斷程式 範例: ``` fun main(args:Array<String>) { var str1:String?="4234" println("str1 is ${check(str1)}") str1=null println("str1 is ${check(str1)}") } fun check(str: String?):String? { str?: throw IllegalStateException("str can not be null") return str } ``` 輸出 ``` str1 is 4234 Exception in thread "main" java.lang.IllegalStateException: str can not be null ``` ### 自訂例外 建立一個class來做為自訂例外 ``` fun main(args:Array<String>) { var str1:String?="4234" println("str1 is ${check(str1)}") str1=null println("str1 is ${check(str1)}") } fun check(str: String?):String? { str?: throw MyException() return str } class MyException(): IllegalStateException("str can not be null") ``` 輸出 ``` str1 is 4234 Exception in thread "main" MyException: str can not be null ``` ### try/catch 可以再拋出例外時做額外操作並不中斷程式 ``` fun main(args:Array<String>) { var str1:String?="4234" println("str1 is ${check(str1)}") str1=null try { println("str1 is ${check(str1)}") } catch (e:Exception) { println(e) } } fun check(str: String?):String? { str?: throw MyException() return str } class MyException(): IllegalStateException("str can not be null") ``` 輸出: ``` str1 is 4234 MyException: str can not be null ``` ### 先決條件函數 直接使用導致中斷 checkNotNull()若參數為Null拋出IllegalStateException,否則返回非Null值 require()若參數為false拋出IllegalArgumentException requireNotNull()若參數為Null拋出IllegalStateException,否則返回非Null值 error()若參數為Null拋出拋出IllegalStateException並輸出錯誤訊息,否則返回非Null值 assert()若參數為false拋出AssertionError, 範例: ``` fun main(args:Array<String>) { var str1:String?=null try { checkNotNull(str1,{"str can not be null"}) } catch (e:Exception) { println(e) } var num:Int=50 try { require(num>=10,{ "num<10!!" }) } catch (e:Exception) { println(e) } } ``` 輸出: ``` java.lang.IllegalStateException: str can not be null java.lang.IllegalArgumentException: num<10!! ``` ## <p id = "string">字串處理</p> ### 多行的字串變數宣告方法 使用三個雙影號加住多行字串即可,字串內也會包含了換行的字元,搭配trimIndent()會刪掉每行前為了視覺用的同長度縮排或空格,並使用lines()會刪除\n換行並提取各行 這裡s第一個字元是\n ``` fun main() { var a = intArrayOf(1,2,3) var s =""" line 1 line 2 line 3 """ println(s) println(s.trimIndent()) s.trimIndent().lines().forEach { println(it) } } ``` ``` line 1 line 2 line 3 line 1 line 2 line 3 line 1 line 2 line 3 ``` ### 跳脫字元 由於一些自字元可能是關鍵字元或是有特別意義的字元,必須在前面加上\來特別顯示出 \t:tab鍵 \b:返回鍵 \n:換行 \r:enter鍵 \":雙引號 \':單引號 \\:反斜線 \$:錢符號 \u:Unicode字元(加上unicode編號) ### string所內建函數 Boolean的參數通常為用來確定是否忽略大小寫的差異,預設為false(視大小寫為相異) **indexOf(Char,Int=0,Boolean=false)** 第一個為要找的字元,第二個為開始找的index 回傳第一次出現該字元的index值,若不存在會回傳-1 範例: ``` fun main(args:Array<String>) { var str1:String="apple' b' de" println(str1.indexOf('\\'')) println(str1.indexOf('\\'',str1.indexOf('\\'')+1)) println(str1.indexOf('\\'',9)) } ``` 輸出: ``` 5 8 -1 ``` **substring(Range),substring(Int,Int)** 第一種可以放印出範圍(until建立的範圍不含上限) 第二種可以指明開始index和結束index(預設為結尾) 回傳字串 範例: ``` fun main(args:Array<String>) { var str1:String="apple' bad de" println(str1.substring(0 until str1.indexOf('\''))) println(str1.substring(7..10 )) println(str1.substring(7,10)) println(str1.substring(7)) } ``` 輸出: ``` apple bad bad bad de ``` **split(String,Boolean=false,Int)** 分割字串根據分割的字串,並回傳list 範例: ``` fun main(args:Array<String>) { var str1:String="apple/ banana/cat" var list=str1.split("/") for(item in list) println(item) var(substr1,substr2)=str1.split("/", limit = 2) println(substr1) println(substr2) list=str1.split("/ ") for(item in list) println(item) } ``` 輸出: ``` apple banana cat apple banana/cat apple banana/cat ``` **replace(Regex){}** 對字串找字元做替換,最後輸出回傳替換完的字串 範例: ``` fun main(args:Array<String>) { var str1= "AaBbCc1234" val str2=str1.replace(Regex("[ABCabc]")){ when(it.value){ "A"->"a" "a"->"A" "B"->"b" "b"->"B" "C"->"c" "c"->"C" else->it.value } } println(str2) } ``` 輸出: ``` aAbBcC1234 ``` ## <p id = "integer">數字</p> 有號數和無數不能直接做運算 ### 有號數類別 Byte:8bits Short:16bits Int:32bits Long:64bits Float:32bits Double:64bits ### 無號數類別 在有號數類別前加上U(unsigned) UByte:8bits UShort:16bits UInt:32bits ULong:64bits UFloat:32bits UDouble:64bits ### 數字間轉換 數字.to類別() 為數字類別擁有的函數可以回傳一個該型態的相同值,不改變原值(bit表示上的相同) 例如.toInt(),.toUBtye() 範例: ``` fun main(args:Array<String>) { var num:UInt = 10u println((9).toUInt()-num) println(((9).toUInt()-num).toInt()) println(9-num.toInt()) println(num is UInt) } ``` 輸出: ``` 4294967295 -1 -1 true ``` ### 字串轉數字 跟數字間轉換一樣,但還有加上 .to類別orNull(),這樣可以避免當出現轉換不了所導致的例外,當轉換不了則回傳Null ### 更精確的小數計算 double在運算時為求速度會只求近似值,因此若要更精準應使用BigDecimal類型(要從Java import) 範例: ``` import java.math.BigDecimal fun main(args:Array<String>) { var a:BigDecimal println(8.45-5.67) println((8.45).toBigDecimal()-(5.67).toBigDecimal()) } ``` 輸出: ``` 2.7799999999999994 2.78 ``` ### 格式化輸出 .format() 可以格式化輸出(規則跟C/C++和Java相同),回傳字串 小數捨去部分採四捨五入 範例: ``` fun main(args:Array<String>) { var num1:Double = 4.044 var num2:Int = 156 println("%.2f".format(num1)) num1 = 4.045 println("%.2f".format(num1)) println("%4d".format(num2)) } ``` 輸出: ``` 4.04 4.05 156 ``` ## <p id = "class">類別</p> 類別屬性一定要有初始值,因為要符合kotlin空值安全的規則 若沒有指定可見性則預設為public 傳入參數前面加上_可辨別為臨時參數,傳入參數只能用於初始化屬性,無法在類別內其他地方呼叫 也可以為那些參數放上預設值,當沒傳入引述時,可使用 ### 主建構函數 在定義類別名後跟隨的()為是主建構函數,會直接或間皆將傳入參數用於定義屬性 ### 宣告方式1: 傳入參數 ``` class 名稱(傳入參數1:參數1型態,...){ 類別屬性 類別函數 } ``` 例子: ``` class Person(_age:Int) { var age = _age fun isAdult():Boolean = age>=18 } ``` ### 宣告方式2: 類別層級屬性 直接建構,若該屬性需要自訂getter或setter則無法這樣使用 ``` class 名稱(var 類別屬性1:類別屬性1型態,...){ 類別函數 } ``` 例子: ``` class Person(var name:String, _age:Int) { var age:Int = _age set(value){ field = if (value>150) 0 else value } } ``` ### 可見性修飾符 由上到下為可見性大到小,internal和protected為無法比較 public:類別外部可見,為**預設** internal:同一模組可見 protected:類別內部可見和其子類別內部可見 private:類別內部可見 ### 屬性getter 和 setter 不管在哪呼叫屬性必須透過預設getter存取,var則必須透過預設setter修改,而可以自訂getter和setter來達到不一樣的效果,並且為setter加上不多於屬性的可見性(setter必須跟屬性相同可見性) getter和setter內包含關鍵詞field代表該屬性 初始化的值不會經過自訂的setter設定 覆寫方法: 屬性 = 值 get() = set(newValue) = { } 範例: ``` fun main(args: Array<String>) { val person = Person("josh") println(person.name) person.name = "lemoncaster" println(person.name) } class Person(name:String) { var name = name get() = field.uppercase() set(newName) { if (newName.length<10) field = newName } } ``` 輸出: ``` JOSH JOSH ``` ### 計算屬性 通常屬性都有field作為存放值的地方,但計算屬性不需要,也不用初始化值,因為只有被呼叫時,才產生值 每次輸出都不同,即透過getter回傳值,儘管可能是固定值 ``` fun main(args: Array<String>) { val r = RandomNum() for(i in 1..5) { println(r.num) } } class RandomNum() { val num get() = (1..100).shuffled().first() } ``` 這樣也合法 ``` class RandomNum() { val num get() = 10 } ``` ### 次建構函數 使用可以用來更加靈活的定義其他建構狀況,此外,必須一定要先經過主建構式,才會進入次建構式,也因此部分參數要符合主建構式 this()是用來呼叫主建構式 宣告方式: ``` constructor(次建構式傳入的參數) : this(要傳入主建構式的參數) ``` 例子: ``` fun main(args: Array<String>) { val person1 = Person("josh",70, 180) //person.test(190) println("${person1.name} ${person1.age} ${person1.height}") val person2 = Person("Hover") println("${person2.name} ${person2.age} ${person2.height}") } class Person(_name:String = "AS",var age:Int = 20) { var name = _name var height = 165 constructor(_name: String, _age : Int, _height:Int) : this(_name, _age) { height = if (age>50) _height - 5 else _height } constructor(_name: String) : this(_name, _age=10, _height = 130) } ``` 輸出: ``` josh 70 175 Hover 10 130 ``` ### 具名傳入 在呼叫時,可以指定傳入給哪個參數,如此一來更明瞭且不用照順序 範例: ``` fun main(args: Array<String>) { val person = Person( _age=20,_name="Hover", _height = 130) println("${person.name} ${person.age} ${person.height}") } class Person(_name:String = "AS",var age:Int = 20) { var name = _name var height = 165 constructor(_name: String, _age : Int, _height:Int) : this(_name, _age) { height = if (age>50) _height - 5 else _height } constructor(_name: String) : this(_name, _age=10, _height = 130) } ``` 此外例如在第一個函數有預設值而第二個沒有時使用,不然會發生錯誤 範例: ``` fun main(args: Array<String>) { val t = Test(p2=2) } class Test(var p1 : Int = 300,var p2 : Int ) { } ``` ### 初始化區域 即其他語言中的建構式,在類別被建立時執行。 宣告方式: ``` Class() { init { } } ``` 範例: ``` fun main(args: Array<String>) { val person = Person(age=20,_name="Hover", _height = 180) println("${person.name} ${person.age} ${person.height}") } class Person(_name:String = "AS",var age:Int = 20, _height: Int) { var name = _name var height = _height init { if (height > 160) height = 200 } } ``` ### 執行順序(包含宣告) 主建構函數 -> 類別層級定義的屬性 or init{}區塊取決於誰在上面誰先執行 -> 次建構函數 範例: ``` fun main(args: Array<String>) { val t = Test() } class Test(var temp1 : Int, _temp2 : Int = 1) { var temp2 : Int = _temp2 var temp3 = { println("temp3 has been initialized!")} constructor():this(temp1=1) { println("Second constructor $temp1 $temp2") } init { temp3() println("$temp1 $temp2") println("Init Block") temp1 = 2 temp2 = 2 } } ``` 輸出: ``` temp3 has been initialized! 1 1 Init Block Second constructor 2 2 ``` ### 特別成員函數 #### componentN讓類別支援解構 **宣告方式:** operator fun componentN(對象:類別):回傳型態 必須依據規定得函數名宣告並且在前加上operator,來表明運算子多載,data class會自動實作 component1代表解構回傳的第一個值以此類推 #### operator讓類別支援運算子計算 **宣告方式:** operator fun 運算子名(對象:類別):回傳型態 必須依據規定得函數名宣告並且在前加上operator,來表明運算子多載 | 函數名 | 運算子 | | -------- | -------- | | + | plus | | - | minus | | * | times | | / | div | | % | mod | | += | plusAssign | | == | equals (要hashCode一樣) | | \> | compareTo | | [] | get | | .. | rangeTo | | in | contains | 範例: ``` fun main(args: Array<String>) { var v1=vector(1, 3) var v2=vector(5,2) println(v1+v2) } class vector(var x:Int, var y : Int) { override fun toString(): String { return "x:$x y:$y" } operator fun plus(other:vector):vector { return vector(x+other.x, y+other.y) } } ``` 輸出: ``` x:6 y:5 ``` #### infix讓類別支援某函數呼叫省略.() 若在成員函數或泛型函數宣告前加入infix代表這個函數呼叫時可以省略.() 如同建立pair時用的to,並非關鍵字而是使用了infix宣告to函數 不過有三個限制: * 函數是成員函數或擴充函數 * 函數只有一參數 * 參數類別不能為vararg且無預設值 ``` class Car(val v1:Int, val v2:String) { infix fun add(num:Int):Int { return v1+num } operator fun minus(num:Int):Int { return v1-num } operator fun component1()= v1 operator fun component2()= v2 } var a = Car(10,"Man") a add 45 a - 15 var (n1, s1) = a n1 s1 ``` ``` 55 -5 10 Man ``` ### 延遲初始化 針對類別層級屬性延後初始化,並且只能對非空且非原始類別使用 對非空類別使用相當於直接使用可空類別加上要使用前檢查是否空 忘記初始化就使用會出現例外異常 #### isInitialized 用來檢查是否初始化了 範例: ``` fun main(args: Array<String>) { for (i in (1..5)) { val t = Test("Lamb") println(t.name) } } class Test(_name : String) { lateinit var name: String init { if ((1..2).shuffled().first()==2) name = _name if (!::name.isInitialized) name = "Sheep" } } ``` 輸出: ``` Sheep Lamb Sheep Sheep Lamb ``` ### 惰性初始化 等到屬性被參照時才進行初始化 只能用在唯讀屬性 **使用方式:** val 屬性 by laze {} 範例: ``` fun main(args: Array<String>) { val t = Test() println(t.num) } class Test() { val num by lazy{(1..10).shuffled().first()} } ``` ### 繼承 類別預設都是無法被繼承的,必須加上關鍵字open,**預設final** 屬性及函數預設都是無法被覆寫的,必須加上關鍵字open,**預設final** 覆寫後可使用super呼叫與父類別共用的屬性或函數 儘管預設是final,但更改為open一旦被繼承後,就會預設該屬性或函數為open 宣告方式: class 子類別名稱(傳入參數1:參數1型態,...):父類別名稱(傳入參數...) 範例: ``` fun main(args: Array<String>) { val t = SubTest(103, 100) t.print() } open class Test(open var temp1 : Int) { open var temp2 = 100 open fun print(){ print("t1:$temp1 t2:$temp2") } } class SubTest(var temp3 : Int,temp:Int) : Test(temp) { override var temp2 = 101 override var temp1 : Int = super.temp2 override fun print() { super.print() println(" t3:$temp3") } } ``` 輸出: ``` t1:100 t2:101 t3:103 ``` **子類別的實例屬於子類別也屬於父類別** 可說是多型 因此以下code合法 ``` fun main(args: Array<String>) { val c1 : C1 = SubC2(103, 100) val c2 = SubC2(203, 200) myPrint(c1) myPrint(c2) } fun myPrint(t:C1) { println("t1:${t.temp1} t2:${t.temp2}") } open class C1(open var temp1 : Int) { open var temp2 = 100 } open class SubC2(var temp3 : Int, temp:Int) : C1(temp) { override var temp2 = super.temp1 final override var temp1 : Int = super.temp2 } ``` 輸出: ``` t1:100 t2:100 t1:100 t2:200 ``` ### 使用is判別變數是哪類別 範例: ``` fun main(args: Array<String>) { val c = SubC2(103, 100) var result = when (c) { is C1 -> "C1" is SubC2 -> "SubC2" else -> "I don't know!" } println(result) result = when (c) { is SubC2 -> "SubC2" is C1 -> "C1" else -> "I don't know!" } println(result) } open class C1(open var temp1 : Int) { open var temp2 = 100 } open class SubC2(var temp3 : Int, temp:Int) : C1(temp) { override var temp2 = super.temp1 final override var temp1 : Int = super.temp2 } ``` 輸出: ``` C1 SubC2 ``` ### Any類別 在kotlin每個類別都是繼承至超類別Any,換言之,每個類別都是Any的子類別,如同java的java.lang.Object ### 類別轉換 使用as進行類別轉換可以讓某物件被以某類別來看待,可以讓程式更靈活 ### 智慧類別轉換 透過is判定類別若屬實,則編譯器就會將其視為該類別對待,否則可能無法使用 範例: ``` fun main(args: Array<String>) { val c1 = C1(100) val c2 = C2(45,64) myPrint(c1) myPrint(c2) } class C1(val value1 : Int) { fun print() { println("$value1") } } class C2(val value1 : Int, val value2 : Int) { fun print() { println("$value1 $value2") } } fun myPrint(obj : Any) { if (obj is C1) obj.print() else { (obj as C2).print() } } ``` 輸出: ``` 100 45 64 ``` ### 單例物件 singleton單例 當類別只允許一個實例存在時,需要使用object來宣告 不允許任何建構式,不需要特別宣告,在第一次呼叫時,會自動初始化 範例: ``` fun main(args: Array<String>) { Controller.start(100) } object Controller { private var index : Int = 0 init { println("Controller has been initialized") } fun start(index : Int) { this.index = index println("Controller has started ${this.index}") } } ``` 輸出: ``` Controller has been initialized Controller has started 100 ``` ### 單例物件繼承 範例: ``` fun main(args: Array<String>) { val temp = object : Car(8){init { println("${wheels*2}") }} Car2 } open class Car(var wheels : Int ) { } object Car2 : Car(4) { init { println("$wheels") } } ``` 輸出: ``` 16 4 ``` **在外部定義具有全域視域** 範例: ``` fun main(args: Array<String>) { Car2 val temp = object : Car(8) {init { println("${wheels*2}") }} Car3 } open class Car(var wheels : Int ) { } object Car2 : Car(4) { init { println("$wheels") } } object Car3 : Car(20) { init { println("Car3:${Car2.wheels}") } } ``` 輸出: ``` 4 16 Car3:4 ``` ### 資料類別 適合存資料,不能被繼承,且自帶已經過特別實作的**toString equals copy componentN** 解構等函數,與原來定義在Any的這些函數不同 **宣告方式** data class 類別名(主建構式) ### 列舉類別 使用字串來創造描述性更好的類別 **宣告方式** enum class 類別名(主建構式){ 列舉常數名1 列舉常數名2(連結的變數) ...; 自訂函數區 } *呼叫方法:* 類別名.類別常數名 類別名.類別常數名.自訂函數名 ### 界面 可以說是類別,但不能直接實作 當類別間想要有共用屬性但無法透過繼承時可以使用,而且一個類別可以繼承多個介面,但不能繼承多個類別 類別用與繼承一樣的方式實作介面(需override,可在主建構繼承) ~~介面可以預設函數實作細節或屬性的setter和getter,但不能使用到field~~ **宣告方式:** interface 介面名{ 屬性宣告 函數宣告 } 範例: ``` fun main(args: Array<String>) { val plane747 = plane(10) val tank45 = tank(10) plane747.shoot() tank45.shoot() println("remain missile number:\nplane747:${plane747.missileNumber}\ntank45:${tank45.missileNumber}") } interface armable{ var missileNumber:Int fun shoot(){ if (missileNumber>=2) missileNumber -= 2 } } class plane(override var missileNumber: Int):armable { val maxspeed=100 } class tank(override var missileNumber: Int):armable { override fun shoot() { if (missileNumber>=1) missileNumber -= 1 } } ``` 輸出: ``` remain missile number: plane747:8 tank45:9 ``` ### 抽象類別 可以被繼承但一定不產生實體,不可包含未經初始化的屬性,但可以有抽象函數 可以繼承介面或其他類別(含抽象) **宣告方式:** abstract class 抽象類別名() ### 實作comparable介面 必須實作和繼承compareTo()方法,這讓物件可以比大小以及排序等相關方法(例如maxOrNull()), ``` class Item(var v1 : Int,var v2 : Int):Comparable<Item> { override fun compareTo(other: Item): Int = when{ v1 != other.v1 -> v1 compareTo other.v1 else -> v2 compareTo other.v2 } override fun toString(): String { return "Item($v1, $v2)" } } fun main() { var itemList = listOf(Item(10,1), Item(2,5), Item(2,7)).sorted() println(itemList) } ``` ``` [Item(2, 5), Item(2, 7), Item(10, 1)] ``` ## <p id = "generic">泛型</p> 可以使函數或類別更靈活,讓其接受可能未知的類別,提高程式重複利用率 **函數加上泛型的宣告:** fun **<泛型類別名1,泛型類別名2...>** 函數名() 範例: ``` fun main(args: Array<String>) { myprint(10) myprint(2.5f) myprint(364.23) myprint(Mouse()) } fun <T>myprint(obj : T) { when(obj) { is Int -> println("Int:$obj") is Float -> println("Float:$obj") else -> println("Other:$obj") } } class Mouse() { override fun toString(): String { return "I am a mouse." } } ``` 輸出: ``` Int:10 Float:2.5 Other:364.23 Other:I am a mouse. ``` **類別加上泛型的宣告** class 類別名<泛型類別名1,泛型類別名2...>() ### 泛型的運算 泛型無法直接運算,必須透過型轉,可見最後那個範例 ### 限制泛型可能類型 可以限制該泛型類別的限制類別或介面 透過下列方式 <泛型類別:限制類別> **當然包含該限制類別的子類別,代表可以讓某些類別繼承某類別或介面來達成限制** ### 逆變 協變 in out 可以指定該泛型類別是否能被改寫或只能讀 in表示不可讀可寫,所以該類別的屬性只能為區域宣告 out表示可讀不可寫,所以該類別的屬性只能為val宣告,便能保證其總是為同一類型 in的範例: ``` class Room<in T:Animal>(animal: T) { } fun main(args: Array<String>) { var room1 = Room(Animal(100)) var room2 = Room(Cat("Black",34)) room2 = room1 } open class Animal(var height : Int) { } class Cat(var color:String,length : Int):Animal(length) { } ``` out的範例: ``` class Room<out T:Animal>(val animal: T) { } fun main(args: Array<String>) { var room1 = Room(Animal(100)) var room2 = Room(Cat("Black",34)) room1 = room2 println(room1.animal.height) println(room1.animal.color) } open class Animal(var height : Int) { } class Cat(var color:String,length : Int):Animal(length) { } ``` ### reified 允許在執行時保留泛型的類型資訊,須搭配inline (泛型無法直接運算,必須透過型轉) 範例: ``` fun main(args: Array<String>) { println(myFun(23.6)) } inline fun <reified T>myFun(num : T):T { val list1 = listOf(10,20.7,4.2f) var num2 : T while(true) { var temp=list1.shuffled().first() if (temp is T) { num2 = temp break } } return when (num) { is Int -> (num as Int + num2 as Int) as T is Float -> (num as Float + num2 as Float) as T is Double -> (num as Double + num2 as Double) as T else -> throw IllegalArgumentException("Unsupported type") } } ``` 輸出: ``` 44.3 ``` ## <p id = "extensions">擴充</p> 不修改類別內部或不用繼承的方式,讓某類別增加屬性或函數,擴充會讓子類別也能使用 ### 擴充函數 宣告: 跟一般函數一樣,但多了必須在函數名前加上類別. 範例: ``` fun main(args: Array<String>) { val temp = "Hello world!" temp.myPrint() } fun String.myPrint() { println("$this, length: ${this.length}") } ``` 輸出: ``` Hello world!, length: 12 ``` 也可以搭配泛型 範例: ``` fun main(args: Array<String>) { val temp = "Hello world!" temp.myPrint() } fun <T> T.myPrint() { println("${this.toString()}.") } ``` 輸出: ``` Hello world!. ``` ### 擴充屬性 跟計算屬性一樣,沒有field可以存值,因此必須設定setter或getter 範例: ``` fun main(args: Array<String>) { val temp = "AdaAAsdA" println(temp.containA) } val String.containA get()= run { this.contains('d') } ``` 輸出: ``` true ``` ## <p id = "performance">評估效能</p> ### measureNanoTime 傳入匿名函數測量該執行花費納秒時間 回傳納秒 ### measureTimeInMillis 傳入匿名函數測量該執行花費豪秒時間 回傳毫秒 ## <p id = "java">與java互動</p> 在kotlin可以值直接建立起java類別物件 範例: Main.kt ``` fun main() { val temp=classA() println(temp.giveString()) } ``` classA.java ``` public class classA { public String giveString() { return "4342abc"; } } ``` 輸出: ``` 4342abc ``` ### 可空性差異 java裡所有變數都可能是Null,所以這會造成kotlin編譯器無法判定是否為空或不是,這種模寧兩可的類型稱為平台類型(platform type) 解決方法為在java那方加上@Nullable註記(不用也不會報錯),這樣便能在kotlin那方使用安全呼叫子 保證為不空則標記為@NotNull ### 類型映射 kotlin所有資料型態都是類別,但java原始資類別不是物件 kotlin的類別大多都能對應到java的類別一一對應,但如果是對應到kotlin基礎資料類型,java是用原始資料類型(非類別class),但在kotlin仍能對java的原始資料變數做kotlin的其對應的類別的類別函數 例如:java的int,在kotlin對應到的是Int,所以可以用Int的類別函數 範例: Main.kt ``` fun main() { val temp=classA() println(temp.num.inc()) } ``` classA.java ``` public class classA { int num=10; } ``` 輸出: ``` 11 ``` ### Getter Setter互通性 在kotlin對類別內成員私有變數設定setter和getter時,使用起來可以直接存取,但在java時,若該變數是私有,則必須透過get或set開頭的函數去存取 因此,對於有get或set開頭的函數,kotlin內仍可以直接存取。 範例: Main.kt ``` fun main() { val temp=classA() println(temp.num) temp.num = 42 println(temp.num) } ``` classA.java ``` public class classA { private int num=10; public void setNum(int num){ this.num = num*10; } public int getNum() { return num; } } ``` 輸出: ``` 10 420 ``` ### java內使用kotlin函數 透過針對kotlin檔案在Jvm內名稱呼叫可以反過來呼叫使用 在kotlin內可以用@file:JvmName()自訂檔案在Jvm名稱,預設為檔名Kt 範例: Main.kt ``` @file:JvmName("KtFile") fun main() { } fun ktPrint(string: String) { println("-----\n $string \n-----\n") } ``` classA.java ``` public class classA { private int num=10; public void setNum(int num){ this.num = num*10; } public int getNum() { return num; } public static void main(String[] args) { KtFile.ktPrint("Hello world!"); } } ``` 輸出: ``` ----- Hello world! ----- ``` 在java中並沒有預設參數,所以某些函數在編譯成java只會有一個方式,並不會自己先生成多個多載方式,來讓java內呼叫kotlin的函數的方式更貼近kotlin內的呼叫方法(仍有無法一樣的呼叫方式) 在函數前手動加入@JvmOverLoads會讓Jvm自動產生最多可能的多載方式 範例: Main.kt (若沒加@JvmOverloads java檔案那呼叫會出錯) ``` @file:JvmName("KtFile") fun main() { } @JvmOverloads fun ktPrint(string1: String = "Apple", string2: String = "Cat") { println("-----\n $string1 \n-----\n") println("-----\n $string2 \n-----\n the End") } ``` classA.java ``` public class classA { private int num=10; public void setNum(int num){ this.num = num*10; } public int getNum() { return num; } public static void main(String[] args) { KtFile.ktPrint("Hello world!"); } } ``` 輸出: ``` ----- Hello world! ----- ----- Cat ----- the End ``` ### Java內存取kotlin類別的變數 java對類別內變數的管理方式是欄位和getter setter,可直接讀取欄位 kotlin對類別內變數的管理方式是屬性跟支援欄位,需借助存取器存取變數(語法上看起來一樣) 因此一般來說Java內存取kotlin類別的變數必須透過get變數名來存取 但可對屬性(要有field的屬性,例如計算屬性就不能)前加上@JvmField,來達到直接存取 若是用在伴生物件的屬性上,可免除先參照伴生物件再呼叫存取器 若是用在伴生物件的函數上,則是使用@JvmStatic,可免除先參照伴生物件再呼叫存取器 範例: Main.kt (若沒加@JvmOverloads java檔案那呼叫會出錯) ``` @file:JvmName("KtFile") public class Car() { var wheels = 4 @JvmField var lights = 2 val num get() = (1..10).shuffled().first() companion object{ @JvmField val maxNum = 10 val minNum = 0 @JvmStatic fun myPrint() { println("myPrint is working!") } } } ``` classA.java ``` public class classA { private int num=10; public void setNum(int num){ this.num = num*10; } public int getNum() { return num; } public static void main(String[] args) { var car = new Car(); System.out.println(car.getWheels()); System.out.println(car.lights); System.out.println(Car.maxNum); System.out.println(Car.Companion.getMinNum()); Car.Companion.myPrint(); } } ``` 輸出: ``` 4 2 10 0 myPrint is working! ``` ### java和kotlin拋出異常 kotlin屬於未檢查異常 java屬於檢查異常 在kotlin呼叫java的拋出異常可以執行,但在java呼叫kotlin的拋出異常會出現編譯不過,因為編譯器認為該沒有要檢查的異常 要加上@Throws(Exception::class) 範例: Main.kt ``` @file:JvmName("KtFile") import java.io.IOException @Throws(IOException::class) fun giveException() { throw IOException() } ``` classA.java ``` import java.io.IOException; public class classA { public static void main(String[] args) { try { KtFile.giveException(); } catch (IOException e) { System.out.println("Something wrong!"); } } } ``` 輸出: ``` Something wrong! ``` ### 在java呼叫函數類別變數 需透過引入kotlin額外函式庫FnctionN達成,並使用invoke傳遞引數 範例: Main.kt ``` @file:JvmName("KtFile") val strNum = { str:String->str.length} @JvmName("getStrNum1") fun getStrNum(): (String) -> Int { return strNum } ``` classA.java ``` import kotlin.jvm.functions.Function1; public class classA { public static void main(String[] args) { Function1<String, Integer> strNum=KtFile.getStrNum(); System.out.println(strNum.invoke("Apple")); System.out.println(strNum.invoke("kit")); } } ``` 輸出: ``` 5 3 ```