集合創(chuàng)建
像創(chuàng)建一個(gè)數(shù)組一樣初始化一個(gè)含有默認(rèn)值的集合癣猾。避免了先創(chuàng)建,再賦值,這一點(diǎn)在java中是做不到的
- 普通集合
//Java
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
//Kotlin
val list = listOf(1,2,3)
- 鍵值對(duì)集合
val map = mapOf(1 to "one",2 to "two",3 to "three")
Kotlin 中雙冒號(hào) :: 使用
Kotlin 中 雙冒號(hào)操作符 表示把一個(gè)方法當(dāng)做一個(gè)參數(shù)晚唇,傳遞到另一個(gè)方法中進(jìn)行使用,通俗的來講就是引用一個(gè)方法杨何。eg:
fun main(args: Array<String>) {
println(lock("param1", "param2", ::getResult))
}
/**
* @param str1 參數(shù)1
* @param str2 參數(shù)2
*/
fun getResult(str1: String, str2: String): String = "result is {$str1 , $str2}"
/**
* @param p1 參數(shù)1
* @param p2 參數(shù)2
* @param method 方法名稱
*/
fun lock(p1: String, p2: String, method: (str1: String, str2: String) -> String): String {
return method(p1, p2)
}
這里需要注意的是酱塔,lock 函數(shù) 的第三個(gè)參數(shù)傳入 method 時(shí),要確定參數(shù)個(gè)數(shù)危虱、類型羊娃、返回值都和其形參一致。
輸出結(jié)果:
result is {param1 , param2}
需要調(diào)用其他 Class 中的某一個(gè)方法時(shí):
fun main(args: Array<String>) {
var d = Test()
println(lock("param1", "param2", d::getResult))
}
Class 的內(nèi)部方法時(shí)調(diào)動(dòng)方式為:
class Test1 {
fun isOdd(x: Int) = x % 2 != 0
fun test() {
var list = listOf(1, 2, 3, 4, 5)
println(list.filter(this::isOdd))
}
}
一般情況埃跷,調(diào)用當(dāng)前類的方法 this 都是可省略的
集合中的高階函數(shù)
map
map通常用于集合的映射蕊玷,例如:
val oldList = listOf(1, 2, 3, 4, 5)
val newList = ArrayList<Int>()
println("集合映射--傳統(tǒng)寫法")
oldList.forEach {
val newElement = it * 2 + 3
newList.add(newElement)
}
println("集合映射--Kotlin寫法邮利,使用map")
val newList1 = oldList.map {
it * 2 + 3
}
flatMap
flatMap通常用于扁平化集合,就是把集合的集合扁平化成集合垃帅,例如:
println("扁平化集合")
val list = listOf(
1..20,
2..5,
3..4
)
val newList3 = list.flatMap {
it
}
newList3.forEach(::println)
flatMap結(jié)合map進(jìn)行變換延届,例如:
println("扁平化集合+變換")
val newList4 = list.flatMap {
it.map {
"NO.$it"
}
}
newList4.forEach(::println)
reduce
reduce通常用于求和,例如:
println("求和")
var sum = oldList.reduce { acc, i -> acc + i }
println(sum)
利用reduce求階乘:
var res2 = (1..5).map(::factorial)
println(res2)
//求n的階乘:n!=1×2×3×...×n
fun factorial(n: Int): Int {
if (n == 0) {
return 1
} else {
return (1..n).reduce { acc, i -> acc * i }
}
}
fold
fold通常用于求和并且加上一個(gè)初始值贸诚,例如:
sum = (1..5).fold(5) { acc, i -> acc + i }
println(sum)
fold還可以進(jìn)行變換方庭,進(jìn)行字符串的連接:
var res = (1..5).fold(StringBuilder()) { acc, i ->
acc.append(i).append(",")
}
println(res)
//也可以這樣寫
var res1 = (1..5).joinToString(",")
println(res1)
filter
filter用于過濾,如果傳入的表達(dá)式的值為true的時(shí)候就保留:
//表達(dá)式為true的時(shí)候保留元素
val newList5 = oldList.filter {
it == 2 || it == 4
}
println(newList5)
包括filterIndexed 的用法也很類似:
oldList.filterIndexed { index, i ->
index == 1
}
中綴函數(shù)
這val map = mapOf(1 to "one", 2 to "two")里的to就是一個(gè)infix function酱固。其源碼實(shí)現(xiàn):
public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)
普通函數(shù)的使用方法都是通過對(duì)象.函數(shù)名實(shí)現(xiàn)的械念,在kotlin中為了方便我們的使用引入了另外一種函數(shù)類型——中綴函數(shù)。這種函數(shù)通過infix關(guān)鍵字定義运悲,有且只有一個(gè)參數(shù)龄减,通過放在對(duì)象之后直接使用,在函數(shù)名之后緊接著的是該函數(shù)傳入的值班眯。中間無需.或()等特殊符號(hào)鏈接希停。
兩個(gè)關(guān)鍵要素:
- 1.使用infix在fun前修飾
- 2.只能有一個(gè)參數(shù)
//中綴函數(shù)
infix fun String.getChar(position: Int) = this.get(position)
val s = "Hello world"
val c = s getChar 1
//普通函數(shù)
fun String.getChar(position: Int) = this.get(position)
val s = "Hello world"
val c = s.getChar(1)
字符串比較
//普通使用字符串對(duì)比調(diào)用StringUtils.equals(strA, strB)
fun main(args: Array<string>) {
val strA = "A"
val strB = "B"
if (StringUtils.equals(strA, strB)) {//這里對(duì)比字符串是了apache中的StringUtils
println("str is the same")
} else {
println("str is the different")
}
}
//利用中綴調(diào)用sameAs對(duì)比兩個(gè)字符串
fun main(args: Array<string>) {
val strA = "A"
val strB = "B"
if (strA sameAs strB) {//中綴調(diào)用 sameAs
println("str is the same")
} else {
println("str is the different")
}
}
判斷一個(gè)元素是否在集合中
//普通調(diào)用集合contains方法判斷元素是否在集合中
fun main(args: Array<string>) {
val list = listOf(1, 3, 5, 7, 9)
val element = 2
if (list.contains(element)) {
println("element: $element is into list")
} else {
println("element: $element is not into list")
}
}
//利用中綴調(diào)用into判斷元素是否在集合中
fun main(args: Array<string>) {
val list = listOf(1, 3, 5, 7, 9)
val element = 2
if (element into list) {//中綴調(diào)用,這樣的寫法鳖敷,會(huì)更加接近我們自然語言的表達(dá)脖苏,更容易理解
println("element: $element is into list")
} else {
println("element: $element is not into list")
}
}
頂層函數(shù)和屬性
java ----- 萬物皆為對(duì)象
kotlin ----- 打破了這種思維定式
Java中本著”萬物皆為對(duì)象“的設(shè)計(jì)原理,所有的函數(shù)和屬性都是依附于class來實(shí)現(xiàn)的定踱,因此很多獨(dú)立的工具方法我們不得不為其創(chuàng)建一個(gè)類來進(jìn)行管理棍潘,也就是我們常說的工具類。這樣的實(shí)現(xiàn)其實(shí)是有些奇怪的崖媚,但是在Kotlin編程里為我們解決了這樣的煩惱亦歉。
- 頂層函數(shù)
我們可以在任何一個(gè)普通的.kt文件中聲明我們想要的函數(shù),并且我們不需要為該函數(shù)創(chuàng)建包裝類畅哑,該函數(shù)就是函數(shù)本身肴楷,就像C語言一樣,以函數(shù)為基本單位而不是類荠呐。
package com.dy.comm.kotlinapplication
fun log(message: String?) {
Log.i("TAG", message)
}
最終會(huì)編譯成Java字節(jié)碼赛蔫,所以實(shí)現(xiàn)代碼其實(shí)是這樣的:
package com.dy.comm.kotlinapplication
public class XXX() {
public static void log(String message) {Log.i("TAG", message)}
}
優(yōu)點(diǎn):方便了編寫代碼
- 頂層屬性
和頂層函數(shù)類似,可以用于存儲(chǔ)一些固定常用量泥张,eg:URL等呵恢。
Kotlin中的解構(gòu)聲明
把一個(gè)對(duì)象的成員解構(gòu)成多個(gè)變量,稱為解構(gòu)聲明
componentN是操作符(類似加減乘除的運(yùn)算符),重載操作符必需要用operator修飾以允許使用!
解構(gòu)聲明componentN函數(shù)的定義如下:
class User(val first: String, val second: String) {
//componentN是操作符,重載它,必須添加operator修飾符
operator fun component1(): String {
return first
}
operator fun component2(): String {
return second
}
}
fun main(args: Array<String>) {
//解構(gòu)聲明會(huì)創(chuàng)建多個(gè)變量,可獨(dú)立使用
val (f, s) = User("lioil", "win")
println("$f, $s") //輸出"lioil", "win"
}
數(shù)據(jù)類(data class)
編譯器會(huì)為數(shù)據(jù)類(data class)自動(dòng)聲明/定義componentN()函數(shù),可直接用解構(gòu)聲明!
data class User(val name: String, val id: Int)
fun main(args: Array<String>) {
val u = User("lioil.win", 1)
//傳統(tǒng)用法
println("${u.name}, ${u.id}")//輸出: lioil.win, 1
//解構(gòu)聲明
val (n, i) = u
println("$n, $i")//輸出: lioil.win, 1
//直接調(diào)用componentN函數(shù)
println("${u.component1()}, ${u.component2()}")
}
函數(shù)返回多個(gè)變量(Return Values)
如果需要一個(gè)函數(shù)返回多個(gè)變量,Kotlin最簡(jiǎn)潔的實(shí)現(xiàn)是聲明一個(gè)數(shù)據(jù)類并返回其實(shí)例對(duì)象,
數(shù)據(jù)類(data class)自動(dòng)聲明/定義componentN()函數(shù),無需我們定義!
data class Result(val result: Int, val status: Status)
fun deFun(...): Result {
return Result(result, status)
}
//函數(shù)直接返回多個(gè)變量,非常方便使用
val (result, status) = deFun(...)
for循環(huán)-解構(gòu)聲明
collection的元素類必須要聲明component1()媚创、component2()等函數(shù)
for ((a, b) in collection) {
print(a)
...
}
映射Map-解構(gòu)聲明
在kotlin標(biāo)準(zhǔn)庫(standard library)提供以下擴(kuò)展:
//iterator()用于map迭代遍歷(循環(huán))
operator fun <K, V> Map<K, V>.iterator(): Iterator<Map.Entry<K, V>> = entrySet().iterator()
//component1渗钉、component2用于解構(gòu)Map.Entr對(duì)象,獲取鍵值對(duì)(key,value)
operator fun <K, V> Map.Entry<K, V>.component1() = getKey()
operator fun <K, V> Map.Entry<K, V>.component2() = getValue()
Map解構(gòu)聲明的實(shí)例:
fun main(args: Array<String>) {
val map = hashMapOf<String, Int>()
map.put("one", 1)
map.put("two", 2)
//(key, value) in map
for ((key, value) in map) {
println("key = $key, value = $value")
}
}
函數(shù)默認(rèn)值
在Java中經(jīng)常會(huì)因?yàn)槟硞€(gè)函數(shù)需要傳入的參數(shù)數(shù)量不同而需要寫很多同名的函數(shù),即方法的重載
在Kotlin的函數(shù)規(guī)則中钞钙,可以為每個(gè)參數(shù)設(shè)置一個(gè)默認(rèn)值鳄橘,在沒有傳入該參數(shù)時(shí)啟用默認(rèn)值
注意:可以省略的只有排在末尾的參數(shù)声离。
fun addUser(name: String = "無名", age: Int = 100, sex: Char = '男', id: Int = -1) {}
addUser()
addUser("張三")
addUser("李四", 10)
addUser("王五", 10, '男', 1)
如果使用命名參數(shù)就可以指定你要設(shè)置的任何參數(shù)。
addUser(age = 5, id = 10)
addUser("趙六",sex = '妖')
命名參數(shù)
命名參數(shù)可以在我們調(diào)用某個(gè)方法時(shí)顯式的寫出我們傳入的參數(shù)名以及對(duì)應(yīng)值瘫怜,這樣能提高我們代碼的可閱讀性术徊。
建議多使用命名參數(shù)來增加代碼的可讀性
fun test(key:Int,value:String)
test(key = 1,value = "one")
既然借助編輯器已經(jīng)能夠?qū)崿F(xiàn)優(yōu)化閱讀的效果,那為什么還要引入命名參數(shù)呢宝磨?雞肋弧关?
當(dāng)使用函數(shù)默認(rèn)值時(shí),本來是只能通過參數(shù)順序進(jìn)行參數(shù)傳入和缺省唤锉,但是有了命名參數(shù)后可以完全忽略參數(shù)位置世囊,直接賦值給你想要的任何參數(shù)。
fun addUser(name: String = "無名", age: Int = 100, sex: Char = '男', id: Int = -1) {}
addUser(age = 5, id = 10)
addUser("趙六",sex = '妖')
賦值為空
Java中有一個(gè)判斷是
if((str=bufferedReader.readLine())!= null)
這句話到了kotlin轉(zhuǎn)不過來
最初我是這么寫的
var str=bufferedReader.readLine()
while(str != null){
執(zhí)行代碼
}
可是讀到bufferedReader.readLine()=null時(shí)報(bào)錯(cuò)了
java.lang.IllegalStateException: bufferedReader.readLine() must not be null
問題在哪呢窿祥?問題在str的聲明上
改成
var str: String ?=null
str=bufferedReader.readLine()
while (str != null) {
buffer.append(str)
str = bufferedReader.readLine()
}
就不再報(bào)錯(cuò)了株憾,因?yàn)樵诤笠环N聲明方式中str允許為空了
return
在kotlin線程中return時(shí)會(huì)報(bào)錯(cuò),正確的方式是
return@Runnable
領(lǐng)域特定語言(DSL)
DSL是什么
編程語言晒衩,比如Java嗤瞎、kotlin都屬于編程語言,而DSL全稱為“領(lǐng)域特定語言”听系。以下為兩者的對(duì)比:
- 編程語言:有一系列足夠完善的能力來解決幾乎所有能被計(jì)算機(jī)解決的問題贝奇。
- DSL:專注在特定任務(wù),或者說領(lǐng)域上靠胜,并放棄與該領(lǐng)域無關(guān)的功能掉瞳。
可以看出,專門針對(duì)某一特殊功能所存在的語言并且僅能完成這部分功能的語言我們稱之為領(lǐng)域特定語言浪漠。常見的有DSL有SQL和正則表達(dá)式陕习。
內(nèi)嵌DSL
內(nèi)嵌DSL作為kotlin的一種新特性,可以方便我們的開發(fā)址愿。那為什么要使用內(nèi)嵌DSL而不是直接使用呢该镣,以SQL為例。
這些語言為了更有效的完成它們的目標(biāo)响谓,通過會(huì)減少它們提供的功能损合,因此當(dāng)你需要執(zhí)行一個(gè)SQL語句的時(shí)候,不用聲明一個(gè)類或者方法娘纷,每一個(gè)關(guān)鍵字就代表了需要執(zhí)行的操作和類型塌忽,每種類型的操作都有自己獨(dú)特的語法和針對(duì)特定任務(wù)的關(guān)鍵字集合。
并且往往DSL語言更趨向于聲明式失驶,和通用編程語言相反,大部分是命令枣购。
這導(dǎo)致直接使用DSL有個(gè)缺點(diǎn):它們很難與使用通用編程語言的宿主應(yīng)用程序結(jié)合起來使用嬉探。簡(jiǎn)單來說就是語法不同擦耀,需要以字符串等其他形式傳入,不便于及時(shí)糾錯(cuò)涩堤。
示例
Anko SQLite(SQL)
fun getUsers(db: ManagedSQLiteOpenHelper): List<User> = db.use {
db.select("Users")
.whereSimple("family_name = ?", "John")
.doExec()
.parseList(UserParser)
}
Anko Layouts(XML)
verticalLayout {
val name = editText()
button("Say Hello") {
onClick { toast("Hello, ${name.text}!") }
}
}
總結(jié)
kotlin用內(nèi)嵌DSL的方式讓一些特殊領(lǐng)域語言的編寫變得更加容易和統(tǒng)一眷蜓。當(dāng)熟練后甚至可以不用再寫XML文件來布局,不用再寫難記又不方便調(diào)試的SQL語句胎围。
其實(shí)現(xiàn)的原理也很簡(jiǎn)單吁系,實(shí)際上就是普通的方法基于lambda表達(dá)式的形式完成高度簡(jiǎn)潔的代碼風(fēng)格。說白了就是以代碼形式完成以上的功能白魂,不過這些代碼是經(jīng)過kotlin深度封裝和優(yōu)化的汽纤。