本文是在學習和使用kotlin時的一些總結與體會,一些代碼示例來自于網絡或Kotlin官方文檔,持續(xù)更新...
對象相關
-
對象表達式:相當于Java匿名類部類皮胡,在使用的地方被立即執(zhí)行:
val a = 10 val listener = object : Info("submit"),IClickListener { override fun doClick() { println("a:$a") } } listener.doClick() // 打印 a:10 //有時候我們只是需要一個沒有父類的對象,我們可以這樣寫: val adHoc = object { var x: Int = 0 var y: Int = 0 } print(adHoc.x + adHoc.y) //像 java 的匿名內部類一樣,對象表達式可以訪問閉合范圍內的變量 (和 java 不一樣的是栓始,這些變量不用是 final 修飾的) fun countClicks(window: JComponent) { var clickCount = 0 var enterCount = 0 window.addMouseListener(object : MouseAdapter() { override fun mouseClicked(e: MouseEvent) { clickCount++ } override fun mouseEntered(e: MouseEvent){ enterCount++ } }) }
-
對象申明:Kotlin 中我們可以方便的通過對象聲明來獲得一個單例,對象聲明是延遲加載的血当, 在第一次使用的時候被初始化幻赚,對象聲明不是一個表達式,不能用在賦值語句的右邊臊旭,對象聲明不能在局部作用域(即直接嵌套在函數(shù)內部)落恼,但是它們可以嵌套到其他對象聲明或非內部類中,
object MyInfo: Info("submit"),IClickListener { override fun doClick() { println("MyInfo do click, $text") // Log: MyInfo do click, , submit } } fun main(args: Array<String>) { MyInfo.doClick() } //當對象聲明在另一個類的內部時离熏,這個類的實例并不能直接訪問對象申明內部佳谦,而只能通過類名來訪問,同樣該對象也不能直接訪問到外部類的方法和變量 class Site { var name = "菜鳥教程" object DeskTop{ var url = "www.runoob.com" fun showName(){ print{"desk legs $name"} // 錯誤撤奸,不能訪問到外部類的方法和變量 } } } fun main(args: Array<String>) { var site = Site() site.DeskTop.url // 錯誤吠昭,不能通過外部類的實例訪問到該對象 Site.DeskTop.url // 正確 }
-
伴隨(生)對象:相當于靜態(tài)內部類+該類的靜態(tài)屬性喊括,所在的類被加載,伴生對象被初始化(和 Java 的靜態(tài)初始是對應):
class Books(var name: String, val page: Int) { companion object ComBooks{ val a : Int = 10 fun doNote() { println("do note") } } } fun main(args: Array<String>) { Books.ComBooks.doNote() println("Book.a = ${Books.ComBooks.a}") println("-------------") Books.doNote() } // Log do note Book.a = 10 ------------- do note //伴隨對象的成員可以通過類名做限定詞直接使用: class MyClass { companion object Factory { fun create(): MyClass = MyClass() } } val instance = MyClass.create() //在使用了 companion 關鍵字時矢棚,伴隨對象的名字可以省略: class MyClass { companion object { } } //盡管伴隨對象的成員很像其它語言中的靜態(tài)成員郑什,但在運行時它們任然是真正類的成員實例,比如可以實現(xiàn)接口: interface Factory<T> { fun create(): T } class MyClass { companion object : Factory<MyClass> { override fun create(): MyClass = MyClass() } } //如果你在 JVM 上使用 @JvmStatic 注解蒲肋,你可以有多個伴隨對象生成為真實的靜態(tài)方法和屬性
屬性字段相關
-
備用字段:Kotlin中不能有field蘑拯,但在自定義getter/setter的時候需要直接訪問屬性而不是又通過getter/settter來取值賦值(循環(huán)調用)。Kotlin自動提供一個備用字段(field)兜粘,通過它可以直接訪問屬性申窘,沒有使用備用字段時不生成備用字段(使用了setter就會生成),:
//使用field關鍵字 public var fieldProp = "" get() = field set(value) { field = value; } //不生成: val isEmpty: Boolean get() = this.size == 0 //生成: val Foo.bar = 1
-
備用屬性:功能與備用字段類似孔轴。:
private var _table: Map<String, Int>? = null public val table: Map<String, Int> get() { if (_table == null) { _table = HashMap() // 參數(shù)類型是自動推導 } return _table ?: throw AssertionError("Set to null by another thread") }
Kotlin可以像python(@property)一樣把方法變成屬性調用剃法,Kotlin是定義一個屬性復寫get()方法返回某個對象中其他的計算出來的值。
編譯時常量
-
相當于java static finial xxx路鹰,而val 只是fInal 贷洲,一個編譯時常量,一個運行時常量晋柱。使用const必須:
- 在kt文件中(類之外优构,Top-level)或在object{}中
- 必須是基本類型或String
- 必須沒有自定義getter
const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated" @Deprected(SUBSYSTEM_DEPRECATED) fun foo() { ... }
延遲初始化屬性
-
當定義屬性時沒有使用 ? ,那么說明是一個非空屬性雁竞,這時必須要初始化钦椭,如果想在后面的方法中再去賦值要加上lateinit。:
public class MyTest { lateinit var subject: TestSubject @SetUp fun setup() { subject = TestSubject() } @Test fun test() { subject.method() } } //這個修飾符只能夠被用在類的 var 類型的可變屬性定義中,不能用在構造方法中.并且屬性不能有自定義的 getter 和 setter訪問器.這個屬性的類型必須是非空的,同樣也不能為一個基本類型.在一個lateinit的屬性初始化前訪問他,會導致一個特定異常,告訴你訪問的時候值還沒有初始化
復寫屬性
- 屬性可復寫碑诉,在主構造函數(shù)中就可使用override關鍵字作為屬性聲明彪腔。
代理(委托)模式
-
類代理:
Kotlin 在語法上支持代理 ,Derived 類可以繼承 Base 接口并且指定一個對象代理它全部的公共方法:interface Base { fun print() } class BaseImpl(val x: Int) : Base { override fun print() { printz(x) } } class Derived(b: Base) : Base by b fun main() { val b = BaseImpl(10) Derived(b).print() } //在 Derived 的父類列表中的 by 從句會將 b 存儲在 Derived 內部對象联贩,并且編譯器會生成 Base 的所有方法并轉給 b
?
-
代理屬性:
所謂的委托屬性漫仆,就是對其屬性值的操作不再依賴于其自身的getter()/setter()方法,是將其托付給一個代理類泪幌,從而每個使用類中的該屬性可以通過代理類統(tǒng)一管理盲厌,再也不用在每個類中,對其聲明重復的操作方法祸泪。語法:val/var <property name>: <Type> by <expression> //var/val:屬性類型(可變/只讀) //name:屬性名稱 //Type:屬性的數(shù)據(jù)類型 //expression:代理類
使用場景:
- 延遲加載屬性(lazy property): 屬性值只在初次訪問時才會計算
- 可觀察屬性(observable property): 屬性發(fā)生變化時, 可以向監(jiān)聽器發(fā)送通知
- 將多個屬性保存在一個 map 內, 而不是保存在多個獨立的域內
Kotlin標準庫中已實現(xiàn)的代理:
-
延遲加載(Lazy):lazy()是一個函數(shù), 接受一個Lambda表達式作為參數(shù), 返回一個Lazy類型的實例,這個實例可以作為一個委托, 實現(xiàn)延遲加載屬性(lazy property): 第一次調用 get() 時, 將會執(zhí)行 lazy() 函數(shù)受到的Lambda 表達式,然后會記住這次執(zhí)行的結果, 以后所有對 get() 的調用都只會簡單地返回以前記住的結果:
val no: Int by lazy { 200 } val c = 200 fun main(args: Array<String>) { val b = 200 println(no) // Log : 200 println(no) // Log : 200 }
注意:
- var類型屬性不能設置為延遲加載屬性吗浩,因為在lazy中并沒有setValue(…)方法
- lazy操作符是線程安全的。如果在不考慮多線程問題或者想提高更多的性能没隘,也可以使用 lazy(LazyThreadSafeMode.NONE){ … }懂扼,lazy的三個參數(shù)為:
- SYNCHRONIZED:鎖定,用于確保只有一個線程可以初始化[Lazy]實例。
- PUBLICATION:初始化函數(shù)可以在并發(fā)訪問未初始化的[Lazy]實例值時調用幾次阀湿,赶熟,但只有第一個返回的值將被用作[Lazy]實例的值。
- NONE:沒有鎖用于同步對[Lazy]實例值的訪問; 如果從多個線程訪問實例陷嘴,是線程不安全的映砖。此模式應僅在高性能至關重要,并且[Lazy]實例被保證永遠不會從多個線程初始化時使用灾挨。
-
可觀察屬性(Observable):Delegates.observable() 函數(shù)接受兩個參數(shù): 第一個是初始化值, 第二個是屬性值變化事件的響應器(handler).這種形式的委托邑退,采用了觀察者模式,其會檢測可觀察屬性的變化劳澄,當被觀察屬性的setter()方法被調用的時候地技,響應器(handler)都會被調用(在屬性賦值處理完成之后)并自動執(zhí)行執(zhí)行的lambda表達式,同時響應器會收到三個參數(shù):被賦值的屬性, 賦值前的舊屬性值, 以及賦值后的新屬性值秒拔。:
var name: String by Delegates.observable("wang", { kProperty, oldName, newName -> println("kProperty:${kProperty.name} | oldName:$oldName | newName:$newName") }) fun main(args: Array<String>) { println("name: $name") // Log:nam:wang name = "zhang" // Log:kProperty:name | oldName:wang | newName:zhang name = "li" // Log:kProperty:name | oldName:zhang | newName:li } //Delegates.observable(wang, hanler),完成了兩項工作莫矗,一是,將name初始化(name=wang)溯警;二是檢測name屬性值的變化趣苏,每次變化時,都會打印其賦值前的舊屬性值, 以及賦值后的新屬性值梯轻。 ?```
-
Vetoable:Delegates.vetoable()函數(shù)接受兩個參數(shù): 第一個是初始化值, 第二個是屬性值變化事件的響應器(handler),是可觀察屬性(Observable)的一個特例,不同的是在響應器指定的自動執(zhí)行執(zhí)行的lambda表達式中在保存新值之前做一些條件判斷尽棕,來決定是否將新值保存喳挑。:
var name: String by Delegates.vetoable("wang", { kProperty, oldValue, newValue -> println("oldValue:$oldValue | newValue:$newValue") newValue.contains("wang") }) fun main(args: Array<String>) { println("name: $name") println("------------------") name = "zhangLing" println("name: $name") println("------------------") name = "wangBing" println("name: $name") } //Log name: wang ------------------ oldValue:wang | newValue:zhangLing name: wang ------------------ oldValue:wang | newValue:wangBing name: wangBing ?```
-
Not Null:在實際開發(fā)時,我們可能會設置可為null的var類型屬性滔悉,在我們使用它時伊诵,肯定是對其賦值,假如不賦值回官,必然要報NullPointException.一種解決方案是曹宴,我們可以在使用它時,在每個地方不管是不是null歉提,都做null檢查笛坦,這樣我們就保證了在使用它時,保證它不是null苔巨。這樣無形當中添加了很多重復的代碼版扩。在Kotlin中,用委托可以不用去寫這些重復的代碼侄泽,Not Null委托會含有一個可null的變量并會在我們設置這個屬性的時候分配一個真實的值礁芦。如果這個值在被獲取之前沒有被分配,它就會拋出一個異常。
class App : Application() { companion object { var instance: App by Delegates.notNull() } override fun onCreate() { super.onCreate() instance = this } }
-
將多個屬性保存在一個map內:使用Gson解析Json時柿扣,可以獲取到相應的實體類的實例肖方,當然該實體類的屬性名稱與Json中的key是一一對應的。在Kotlin中未状,存在這么一種委托方式俯画,類的構造器接受一個map實例作為參數(shù),將map實例本身作為屬性的委托娩践,屬性的名稱與map中的key是一致的活翩,也就是意味著我們可以很簡單的從一個動態(tài)地map中創(chuàng)建一個對象實例:
class User(val map: Map<String, Any?>) { val name: String by map val age: Int by map } fun main(args: Array<String>) { val user = User(mapOf( "name" to "John Doe", "age" to 25 )) println(user.name) // 打印結果為: "John Doe" println(user.age) // 打印結果為: 25 } //委托屬性將從這個 map中讀取屬性值(使用屬性名稱字符串作為 key 值)。 //如果不用只讀的 Map , 而改用值可變的 MutableMap , 那么也可以用作 var 屬性的委托翻伺。: class User(val map: MutableMap<String, Any?>) { val name: String by map val age: Int by map } fun main(args: Array<String>) { var map:MutableMap<String, Any?> = mutableMapOf( "name" to "John Doe", "age" to 25) val user = User(map) println(user.name) // 打印結果為: "John Doe" println(user.age) // 打印結果為: 25 println("--------------") map.put("name", "Green Dao") map.put("age", 30) println(user.name) // 打印結果為: Green Dao println(user.age) // 打印結果為: 30 }
-
屬性委托的前提條件:getValue()材泄,setValue()。自定義委托必須要實現(xiàn):ReadOnlyProperty和ReadWriteProperty吨岭。取決于我們被委托的對象是val還是var拉宗,如:
public interface ReadWriteProperty<in R, T> { public operator fun getValue(thisRef: R, property: KProperty<*>): T public operator fun setValue(thisRef: R, property: KProperty<*>, value: T) } //定義一個NotNullVar private class NotNullVar<T: Any>() : ReadWriteProperty<Any?, T> { private var value: T? = null public override fun getValue(thisRef: Any?, property: KProperty<*>): T { return value ?: throw IllegalStateException("Property ${property.name} should be initialized before get.") } public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { this.value = value } } //第一個thisRef表示持有該對象的對象, //第二個參數(shù) property 是該值的類型, //第三個參數(shù) value 就是屬性的值了
?
密封類
-
密封類:類的擴展,但每個枚舉常量只存在一個實例,而密封類的一個子類可以有可包含狀態(tài)的多個實例辣辫,雖然密封類也可以有子類旦事,但是所有子類都必須在與密封類自身相同的文件中聲明,間接的子類不受限制急灭。密封類是自身抽象的姐浮,它不能直接實例化并可以有抽象(abstract)成員。密封類不允許有非-private 構造函數(shù)(其構造函數(shù)默認為 private)葬馋。使用密封類的關鍵好處在于使用 when 表達式 的時候卖鲤,能夠驗證語句覆蓋了所有情況,就不需要為該語句再添加一個 else 子句了:
sealed class Expr data class Const(val number: Double) : Expr() data class Sum(val e1: Expr, val e2: Expr) : Expr() object NotANumber : Expr() fun eval(expr: Expr): Double = when(expr) { is Const -> expr.number is Sum -> eval(expr.e1) + eval(expr.e2) NotANumber -> Double.NaN // 不再需要 `else` 子句畴嘶,因為我們已經覆蓋了所有的情況 }
接口
-
與java8類似可有抽象方法與實現(xiàn)方法蛋逾,不可保存狀態(tài),屬性必須是抽象的或唯一值的(無備用屬性)窗悯。
?
擴展
-
不需要在類中去添加方法区匣,在外部就可以給任何地方的類添加我們想要的方法,替換掉java中的FileUtil,xxxUtil等如:
Collections.swap(list, Collections.binarySearch(list, Collections.max(otherList)), Collections.max(list)) //變成 list.swap(list.binarySearch(otherList.max()), list.max())
擴展是被靜態(tài)解析的:擴展實際上并沒有修改它所擴展的類,只是讓這個類的實例對象能夠通過"."調用新的函數(shù)蒋院。需要強調的是擴展函數(shù)是靜態(tài)分發(fā)的亏钩,舉個例子,它們并不是接受者類型的虛擬方法。這意味著擴展函數(shù)的調用是由發(fā)起函數(shù)調用的表達式的(對象)類型決定的悦污,而不是在運行時動態(tài)獲得的表達式的(對象)類型決定铸屉。比如:
open class C class D: C() fun C.foo() = "c" fun D.foo() = "d" fun printFoo(c: C) { println(c.foo()) } printFoo(D()) //輸出 c,因為這里擴展函數(shù)的調用決定于聲明的參數(shù) c 的類型切端,也就是 C彻坛。
如果有同名同參數(shù)的成員函數(shù)和擴展函數(shù),調用的時候會使用成員函數(shù),比如:
class C { fun foo() { println("member") } } fun C.foo() { println("extension") } C().foo() //輸出"member",而不是"extension" //可以通過不同的函數(shù)簽名的方式重載函數(shù)的成員函數(shù): fun C.foo(i:Int) { println("extention") } C().foo(1) //輸出 “extentions”昌屉。
擴展的域:大多數(shù)時候我們在 top level 定義擴展(就在包下面直接定義):
package foo.bar fun Baz.goo() { ... } //為了在除聲明的包外使用這個擴展钙蒙,我們需要在別的文件中使用時導入: //-------------------------------------------------// package com.example.usage import foo.bar.goo // 導入所有名字叫 "goo" 的擴展 // 或者 import foo.bar.* // 導入foo.bar包下得所有數(shù)據(jù) fun usage(baz: Baz) { baz.goo() }
?
-
函數(shù)擴展:
fun <T> MutableList<T>.swap(x: Int, y: Int) { val tmp = this[x] // 'this' corresponds to the list this[x] = this[y] this[y] = tmp } //this 關鍵字對應接收者對象(MutableList<T>) //使用: val l = mutableListOf(1, 2, 3) l.swap(0, 2)// 在 `swap()` 函數(shù)中 `this` 持有的值是 `l`
-
可空的接收者:
使用空接收者類型進行定義。這樣的擴展使得间驮,即使是一個空對象仍然可以調用該擴展躬厌,然后在擴展的內部進行 this == null 的判斷。這樣你就可以在 Kotlin 中任意調用 toString() 方法而不進行空指針檢查:空指針檢查延后到擴展函數(shù)中完成:fun Any?.toString(): String { if (this == null) return "null" // 在空檢查之后竞帽,`this` 被自動轉為非空類型扛施,因此 toString() 可以被解析到任何類的成員函數(shù)中 return toString() }
T.所以擴展是用.來使用的,如Kotlin的to函數(shù)就是A to B 以空格使用屹篓。
-
屬性擴展
注意疙渣,由于擴展并不會真正給類添加了成員屬性,因此也沒有辦法讓擴展屬性擁有一個備份字段.這也是為什么初始化函數(shù)不允許有擴展屬性堆巧。擴展屬性只能夠通過明確提供 getter 和 setter方法來進行定義:
//正確: val <T> List<T>.lastIndex: Int get() = size-1 //錯誤: val Foo.bar = 1 //error: initializers are not allowed for extension properties
?
-
伴隨對象擴展
class MyClass { companion object {} } fun MyClass.Companion.foo(){ } //調用 MyClass.foo()
數(shù)據(jù)類
我們經常創(chuàng)建一個只保存數(shù)據(jù)的類妄荔。在這樣的類中一些函數(shù)只是機械的對它們持有的數(shù)據(jù)進行,如從服務端返回的Json字符串對象映射成Java類谍肤。data類使用:
data class 類名(var param1 :數(shù)據(jù)類型,...){}
data class 類名 可見性修飾符 constructor(var param1 : 數(shù)據(jù)類型 = 默認值,...)
//data為聲明數(shù)據(jù)類的關鍵字啦租,必須書寫在class關鍵字之前。
//在沒有結構體的時候荒揣,大括號{}可省略篷角。
//構造函數(shù)中必須存在至少一個參數(shù),并且必須使用val或var修飾系任。這一點在下面數(shù)據(jù)類特性中會詳細講解内地。
//參數(shù)的默認值可有可無。(若要實例一個無參數(shù)的數(shù)據(jù)類赋除,則就要用到默認值)
// 定義一個名為Person的數(shù)據(jù)類:
data class Preson(var name : String,val sex : Int, var age : Int)
data類必須滿足的條件:
- 主構造函數(shù)需要至少有一個參數(shù)
- 主構造函數(shù)的所有參數(shù)需要標記為 val 或 var;
- 數(shù)據(jù)類不能是抽象非凌、開放举农、密封或者內部的;
- 數(shù)據(jù)類是可以實現(xiàn)接口的敞嗡,如(序列化接口)颁糟,同時也是可以繼承其他類的,如繼承自一個密封類喉悴。
約定俗成的規(guī)定:當構造函數(shù)中的參過多時棱貌,為了代碼的閱讀性,一個參數(shù)的定義占據(jù)一行箕肃。
編輯器為我們做的事情:
- 生成equals()函數(shù)與hasCode()函數(shù)
- 生成toString()函數(shù)婚脱,由類名(參數(shù)1 = 值1,參數(shù)2 = 值2,....)構成
- 由所定義的屬性自動生成component1()障贸、component2()错森、...、componentN()函數(shù)篮洁,其對應于屬性的聲明順序涩维。
- copy()函數(shù)。修改部分屬性袁波,但是保持其他不變瓦阐。
copy函數(shù)的使用:
data class User(val name : String, val pwd : String)
val mUser = User("kotlin","123456")
println(mUser)
val mNewUser = mUser.copy(name = "new Kotlin")
println(mNewUser)
標準庫提供的data類: Pair 和 Triple,源碼如下:
@file:kotlin.jvm.JvmName("TuplesKt")
package kotlin
// 這里去掉了源碼中的注釋
public data class Pair<out A, out B>(
public val first: A,
public val second: B) : Serializable {
// toString()方法
public override fun toString(): String = "($first, $second)"
}
// 轉換
public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)
// 轉換成List集合
public fun <T> Pair<T, T>.toList(): List<T> = listOf(first, second)
// 這里去掉了源碼中的注釋
public data class Triple<out A, out B, out C>(
public val first: A,
public val second: B,
public val third: C ) : Serializable {
// toString()方法
public override fun toString(): String = "($first, $second, $third)"
}
// 轉換成List集合
public fun <T> Triple<T, T, T>.toList(): List<T> = listOf(first, second, third)
?
泛型
-
兩種型變:
協(xié)變:當A ≦ B時,如果有f(A) ≦ f(B),那么f叫做協(xié)變篷牌;
-
逆變:當A ≦ B時,如果有f(B) ≦ f(A),那么f叫做逆變睡蟋;
其余為不變。
協(xié)變娃磺,逆變薄湿,不變來原于子類可以安全的向上轉型為父類。
-
不變(java的泛型<T>是不變的):
ArrayList<Number> list = new ArrayList<Integer>(); //type mismatch
-
協(xié)變:
List<? extends Number> list001 = new ArrayList<Integer>(); List<? extends Number> list002 = new ArrayList<Float>(); Number n1=list001.get(0); Number n2=list002.get(0);
? “? extends Number”則表示通配符”?”的上界為Number偷卧,換句話說就是豺瘤,“? extends Number”可以代表Number或其子類,但代表不了Number的父類(如Object)听诸,因為通配符的上界是Number坐求。于是有“? extends Number” ≦ Number,則List<? extends Number> ≦ List< Number >晌梨。但是這里不能向list001桥嗤、list002添加除null以外的任意對象∽序颍可以這樣理解一下泛领,List<Integer>可以添加Interger及其子類,List<Float>可以添加Float及其子類敛惊,List<Integer>渊鞋、List<Float>都是List<? extends Animal>的子類型,如果能將Float的子類添加到List<? extends Animal>中瞧挤,就說明Float的子類也是可以添加到List<Integer>中的锡宋,顯然是不可行。故java為了保護其類型一致特恬,禁止向List<? extends Number>添加任意對象执俩,不過卻可以添加null。
-
逆變:
List<? super Number> list = new ArrayList<>(); List<? super Number> list001 = new ArrayList<Number>(); List<? super Number> list002 = new ArrayList<Object>(); list001.add(new Integer(3)); list002.add(new Integer(3));
“? super Number” 則表示通配符”?”的下界為Number癌刽。為了保護類型的一致性役首,因為“尝丐? super Number”可以是Object或其他Number的父類,因無法確定其類型宋税,也就不能往List<? super Number >添加Number的任意父類對象摊崭。但是可以向List<? super Number >添加Number及其子類。
-
PECS(《Effective Java》杰赛,producer-extends, consumer-super):協(xié)變只能取(生產者)呢簸,逆變只能寫(消費者),如java的幾個api:
public class Stack<E>{ public Stack(); public void push(E e): public E pop(); public boolean isEmpty(); } //push all // Wildcard type for parameter that serves as an E producer public void pushAll(Iterable<? extends E> src) { for (E e : src) push(e); } //pop all // Wildcard type for parameter that serves as an E consumer public void popAll(Collection<? super E> dst) { while (!isEmpty()) dst.add(pop()); } // java.util.Collections的copy方法 public static <T> void copy(List<? super T> dest, List<? extends T> src) { int srcSize = src.size(); if (srcSize > dest.size()) throw new IndexOutOfBoundsException("Source does not fit in dest"); if (srcSize < COPY_THRESHOLD || (src instanceof RandomAccess && dest instanceof RandomAccess)) { for (int i=0; i<srcSize; i++) dest.set(i, src.get(i)); } else { ListIterator<? super T> di=dest.listIterator(); ListIterator<? extends T> si=src.listIterator(); for (int i=0; i<srcSize; i++) { di.next(); di.set(si.next()); } } }
-
Kotlin 用out 乏屯,in修飾符使協(xié)變與逆變使用起來更方便,out表示只生成根时,in表示只消費,稱之為聲明處變型辰晕。這與 Java 中的使用處變型相反:
abstract class Source<out T> { abstract fun nextT(): T } fun demo(strs: Source<String>) { val objects: Source<Any> = strs // This is OK, since T is an out-parameter // ... } //-------------------------// abstract class Comparable<in T> { abstract fun compareTo(other: T): Int } fun demo(x: Comparable<Number>) { x.compareTo(1.0) // 1.0 has type Double, which is a subtype of Number // Thus, we can assign x to a variable of type Comparable<Double> val y: Comparable<Double> = x // OK! }
-
類型投影
使用處變型:類型投影蛤迎。有些類 不能 限制它只返回 T,如Array含友,T既要返回又要在參數(shù)中消費:
class Array<T>(val size: Int) { fun get(index: Int): T { /* ... */ } fun set(index: Int, value: T) { /* ... */ } } //這個類既不能是協(xié)變的也不能是逆變的替裆,這會在一定程度上降低靈活性【轿剩考慮下面的函數(shù)形式: fun copy(from: Array<out Any>, to: Array<Any>) { // ... }
-
星投影
有時你對類型參數(shù)一無所知辆童,但任然想安全的使用它。保險的方法就是定一個該范型的投影惠赫,每個該范型的正確實例都將是該投影的子類把鉴,F(xiàn)oo<*>
-
范型約束
上界:最常用的類型約束是上界,在 Java 中對應 extends關鍵字儿咱,這里的上界只是約束在給泛型定型時要滿足的條件讲逛。
fun <T : Comparable<T>> sort(list: List<T>) { // ... } sort(listOf(1, 2, 3)) // OK. Int is a subtype of Comparable<Int> sort(listOf(HashMap<Int, String>())) // Error: HashMap<Int, String> is not a subtype of Comparable<HashMap<Int, String>>
默認的上界是 Any?定铜。在尖括號內只能指定一個上界蹦渣。如果要指定多種上界鹏浅,需要用 where 語句指定:
fun <T> cloneWhenGreater(list: List<T>, threshold: T): List<T> where T : Comparable, T : Cloneable { return list.filter { it > threshold }.map { it.clone() } }
嵌套類
-
嵌套類,與java的靜態(tài)內部類相似:
class Outer { private val bar: Int = 1 class Nested { fun foo() = 2 } } val demo = Outer.Nested().foo() //==2
-
內部類钳宪,相當與java的內部類凯旭,持有一個外部類的引用,不能單獨使用:
class Outer { private val bar: Int = 1 inner class Inner { fun foo() = bar } } val demo = Outer().Inner().foo() //==1
-
匿名內部類使套,通過對象表達式創(chuàng)建:
window.addMouseListener(object: MouseAdapter() { override fun mouseClicked(e: MouseEvent) { // ... } override fun mouseEntered(e: MouseEvent) { // ... } }) //如果對象是函數(shù)式的 java 接口的實例(比如只有一個抽象方法的 java 接口),你可以用一個帶接口類型的 lambda 表達式創(chuàng)建它 val listener = ActionListener { println("clicked") }
?
函數(shù)
-
函數(shù)參數(shù)
-
標準參數(shù):
fun powerOf(number: Int, exponent: Int) {
...
}- 默認參數(shù),函數(shù)參數(shù)可以設置默認值,當調用函數(shù)時參數(shù)被忽略會使用默認值鞠柄。這樣相比其他語言可以減少重載: ```kotlin fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size ) { ... }
-
命名參數(shù):在調用函數(shù)時可以用參數(shù)的命名來賦值參數(shù)侦高。這對于那種有大量參數(shù)的函數(shù)很方便:
fun reformat(str: String, normalizeCase: Boolean = true,upperCaseFirstLetter: Boolean = true, divideByCamelHumps: Boolean = false, wordSeparator: Char = ' ') { ... } //調用: //使用默認參數(shù): reformat(str) //調用非默認參數(shù): reformat(str, true, true, false, '_') //使用命名參數(shù): reformat(str, normalizeCase = true, uppercaseFirstLetter = true, divideByCamelHumps = false, wordSeparator = '_' ) //不需要全部參數(shù): reformat(str, wordSeparator = '_') //注意,命名參數(shù)語法不能夠被用于調用Java函數(shù)中,因為Java的字節(jié)碼不能確保方法參數(shù)命名的不變性
默認參數(shù)可能只是給參數(shù)一個默認值,而命名參數(shù)則給參數(shù)一個有意義的名字厌杜。
-
不帶返回值的參數(shù):
如果函數(shù)不會返回任何有用值奉呛,那么他的返回類型就是 Unit .Unit 是一個只有唯一值Unit的類型.這個值并不需要被直接返回:
fun printHello(name: String?): Unit { if (name != null) println("Hello ${name}") else println("Hi there!") // `return Unit` or `return` is optional } //Unit 返回值也可以省略: fun printHello(name: String?) { ... }
-
變長參數(shù):
函數(shù)的參數(shù)(通常是最后一個參數(shù))可以用 vararg 修飾符進行標記:
fun <T> asList(vararg ts: T): List<T> { val result = ArrayList<T>() for (t in ts) result.add(t) return result } //標記后,允許給函數(shù)傳遞可變長度的參數(shù): val list = asList(1, 2, 3) //只有一個參數(shù)可以被標注為 vararg 计螺。加入vararg并不是列表中的最后一個參數(shù),那么后面的參數(shù)需要通過命名參數(shù)語法進行傳值,再或者如果這個參數(shù)是函數(shù)類型,就需要通過lambda法則. //當調用變長參數(shù)的函數(shù)時,我們可以一個一個的傳遞參數(shù)瞧壮,比如 asList(1, 2, 3)登馒,或者我們要傳遞一個 array 的內容給函數(shù),我們就可以使用 * 前綴操作符: val a = array(1, 2, 3) val list = asList(-1, 0, *a, 4)
各種類型參數(shù)定義與使用與Python相似
-
-
單表達式函數(shù):
//當函數(shù)只返回單個表達式時咆槽,大括號可以省略并在 = 后面定義函數(shù)體: fun double(x: Int): Int = x*2 //在編譯器可以推斷出返回值類型的時候,返回值的類型可以省略: fun double(x: Int) = x * 2
?
-
函數(shù)范圍
Kotlin 中可以在文件頂級聲明函數(shù)陈轿,這就意味者你不用像在Java,C#或是Scala一樣創(chuàng)建一個類來持有函數(shù)。除了頂級函數(shù)秦忿,Kotlin 函數(shù)可以聲明為局部的麦射,作為成員函數(shù)或擴展函數(shù):
//局部函數(shù) fun dfs(graph: Graph) { fun dfs(current: Vertex, visited: Set<Vertex>) { if (!visited.add(current)) return for (v in current.neighbors) dfs(v, visited) } dfs(graph.vertices[0], HashSet()) } //局部函數(shù)可以訪問外部函數(shù)的局部變量(比如閉包) fun dfs(graph: Graph) { val visited = HashSet<Vertex>() fun dfs(current: Vertex) { if (!visited.add(current)) return for (v in current.neighbors) dfs(v) } dfs(graph.vertices[0]) } //局部函數(shù)甚至可以返回到外部函數(shù) fun reachable(from: Vertex, to: Vertex): Boolean { val visited = HashSet<Vertex>() fun dfs(current: Vertex) { if (current == to) return@reachable true if (!visited.add(current)) return for (v in current.neighbors) dfs(v) } dfs(from) return false }
-
成員函數(shù)
跟java一樣,類中的成員灯谣。
-
泛型函數(shù)
跟java一樣潜秋。
-
尾遞歸函數(shù)
Kotlin 支持函數(shù)式編程的尾遞歸。這個允許一些算法可以通過循環(huán)而不是遞歸解決問題胎许,從而避免了棧溢出峻呛。當函數(shù)被標記為 tailrec 時,編譯器會優(yōu)化遞歸辜窑,并用高效迅速的循環(huán)代替它:
tailrec fun findFixPoint(x: Double = 1.0): Double = if (x == Math.cos(x)) x else findFixPoint(Math.cos(x)) //使用 tailrec 修飾符必須在最后一個操作中調用自己钩述。在遞歸調用代碼后面是不允許有其它代碼的,并且也不可以在 try/catch/finall 塊中進行使用谬擦。當前的尾遞歸只在 JVM 的后端中可以用
-
高階函數(shù)
高階函數(shù)就是可以接受函數(shù)作為參數(shù)或者返回一個函數(shù)的函數(shù):
fun <T> lock(lock: Lock, body: () -> T ) : T { lock.lock() try { return body() } finally { lock.unlock() } } //body 是一個類型為 () -> T 的函數(shù)切距,可以這樣使用 fun toBeSynchroized() = sharedResource.operation() val result = lock(lock, ::toBeSynchroized) //更方便的是傳一個字面函數(shù)(lambda表達式) val result = lock(lock, { sharedResource.operation() }) //在 kotlin 中有一個約定,如果某一個函數(shù)的最后一個參數(shù)是函數(shù)惨远,并且你向那個位置傳遞了一個 lambda 表達式谜悟,那么,你可以在括號外面定義這個 lambda 表達式: lock (lock) { sharedResource.operation() } //高階函數(shù)map: fun <T, R> List<T>.map(transform: (T) -> R): List<R> { val result = arrayListOf<R>() for (item in this) result.add(transform(item)) return result } //調用: val doubled = ints.map {it -> it * 2} //如果字面函數(shù)只有一個參數(shù)北秽,則聲明可以省略葡幸,名字就是 it : ints.map {it * 2} //這樣就可以寫LINQ-風格的代碼了: strings.filter{ it.length == 5 }.sortedBy{ it }.map{ it.toUpperCase() }
-
字面函數(shù)和函數(shù)表達式(lambda表達式),字面函數(shù)或函數(shù)表達式就是一個 "匿名函數(shù)",也就是沒有聲明的函數(shù)贺氓,但立即作為表達式傳遞下去:
max(strings, {a, b -> a.length < b.length }) //max 函數(shù)就是一個高階函數(shù),它接受函數(shù)作為第二個參數(shù)蔚叨。第二個參數(shù)是一個表達式所以本生就是一個函數(shù),即字面函數(shù)辙培。作為一個函數(shù)蔑水,相當于: fun compare(a: String, b: String) : Boolean = a.length < b.length
如果只有一個參數(shù)lambda中可以不寫參數(shù)變量,直接用it表示參數(shù)扬蕊,如:
{it.length}
-
Lambda表達式接收器:
(函數(shù)字面量接收器,在定義高階函數(shù)參數(shù)時使用)是上面兩者的結合——一個以指定接收器的擴展函數(shù)為參數(shù)的高階函數(shù)搀别。所以在我們傳遞的Lambda表達式中我們可以直接訪問接收器的公共方法和屬性(在接受器的上下文環(huán)境中),就好像在接收器內部一樣:
inline fun FragmentManager.inTransaction(func: FragmentTransaction.() -> Unit) { val fragmentTransaction = beginTransaction() fragmentTransaction.func() fragmentTransaction.commit() } //或: inline fun FragmentManager.inTransaction(func: FragmentTransaction.() -> FragmentTransaction) { beginTransaction().func().commit() } //這就是FragmentManager的擴展函數(shù),接收一個Lambda表達式接收器作為參數(shù),FragmentTransaction作為接收器 //調用 supportFragmentManager.inTransaction { //remove(fragmentA) add(R.id.frameLayoutContent, fragmentB) } //需要說明的是在Lambda表達式中我們調用FragmentTransaction的方法如add或者remove時并沒有使用修飾符,因為這是對FragmentTransaction的擴展函數(shù).
?
-
函數(shù)類型
一個函數(shù)要接受另一個函數(shù)作為參數(shù)尾抑,我們得給它指定一個類型歇父。比如上面的 max:
fun max<T>(collection: Collection<out T>, less: (T, T) -> Boolean): T? { var max: T? = null for (it in collection) if (max == null || less(max!!, it)) max = it return max } //參數(shù) less 是 (T, T) -> Boolean類型蒂培,也就是接受倆個 T 類型參數(shù)返回一個 Boolean:如果第一個參數(shù)小于第二個則返回真。在函數(shù)體第四行榜苫, less 是用作函數(shù)护戳。 //一個函數(shù)類型可以像上面那樣寫,也可有命名參數(shù) val compare: (x: T,y: T) -> Int = ...
-
函數(shù)文本語法
函數(shù)文本的完全寫法:
val sum = {x: Int,y: Int -> x + y} //函數(shù)文本總是在大括號里包裹著垂睬,在完全語法中參數(shù)聲明是在括號內媳荒,類型注解是可選的,函數(shù)體是在 -> 之后羔飞,像下面這樣: val sum: (Int, Int) -> Int = {x, y -> x+y } //函數(shù)文本有時只有一個參數(shù)肺樟。如果 kotlin 可以從它本生計算出簽名,那么可以省略這個唯一的參數(shù)逻淌,并會通過 it 隱式的聲明它 ints.filter {it > 0}//這是 (it: Int) -> Boolean 的字面意思 //注意如果一個函數(shù)接受另一個函數(shù)做為最后一個參數(shù)么伯,該函數(shù)文本參數(shù)可以在括號內的參數(shù)列表外的傳遞
-
函數(shù)表達式
指定返回值的函數(shù)在大多數(shù)情形中是不必要的,因為返回值是可以自動推斷的卡儒。然而田柔,如果你需要自己指定,可以用函數(shù)表達式來做:
fun(x: Int, y: Int ): Int = x + y //函數(shù)表達式很像普通的函數(shù)聲明骨望,除了省略了函數(shù)名硬爆。它的函數(shù)體可以是一個表達式(像上面那樣)或者是一個塊: fun(x: Int, y: Int): Int { return x + y } //參數(shù)以及返回值和普通函數(shù)是一樣的,如果它們可以從上下文推斷出參數(shù)類型擎鸠,則參數(shù)類型可以省略: ints.filter(fun(item) = item > 0)
-
閉包
一個字面函數(shù)或者表達式函數(shù)可以訪問閉包缀磕,即訪問自身范圍外的聲明的變量。不像 java 那樣在閉包中的變量是被捕獲修改的:
var sum = 0 ints.filter{it > 0}.forEach { sum += it } print(sum)
-
函數(shù)表達式擴展
表達式函數(shù)的擴展和普通的擴展區(qū)別是它有接收類型的規(guī)范:
val sum = fun Int.(other: Int): Int = this + other //接收類型必須在表達式函數(shù)中明確指定劣光,但字面函數(shù)不用袜蚕。字面函數(shù)可以作為擴展函數(shù)表達式,但只有接收類型可以通過上下文推斷出來,表達式函數(shù)的擴展類型是一個帶接收者的函數(shù): sum : Int.(other: Int) -> Int //使用 1.sum(2)
字面函數(shù)(lambda)用->分隔函數(shù)體绢涡,函數(shù)表達式用=分隔函數(shù)體牲剃。
-
內聯(lián)函數(shù)
編譯器將使用函數(shù)的定義體來替代函數(shù)調用語句,這種替代行為發(fā)生在編譯階段而非程序運行階段雄可,也就是說把被調用的函數(shù)體復制到調用處凿傅,好處:
- 減少了方法調用,壓棧数苫,出棧的成本聪舒。
- 在kotlin中,函數(shù)就是對象虐急,當你調用某個函數(shù)的時候过椎,就會創(chuàng)建相關的對象,內存的分配戏仓,虛擬調用都有開銷疚宇,內聯(lián)可以減少成本。
使用:
inline fun <T> check(lock: Lock, body: () -> T): T { lock.lock() try { return body() } finally { lock.unlock() } } //---------調用----------------// fun run() { check(l, {"我是lambda方法體"})//l是一個Lock對象 } //編譯器會把調用處換成這樣: fun run() { l.lock() try { return "我是lambda方法體" } finally { l.unlock() } } //如一個函數(shù)是inline的赏殃,那么參數(shù)里的函數(shù)敷待,lambda也默認為inline的。如果要部分參數(shù)為非inline仁热,可以使用noinline關鍵字: inline fun doSomething(a:Int,b:Int,noinline doOther:(a:Int,b:Int)->Int){ //... }
-
非局部返回
Kotlin在lambda中不能直接使用return榜揖,要使用return配合標簽,但如果內聯(lián)抗蠢,則可以在lambda中直接使用return举哟,該return直接作用于調用者函數(shù),也就是說直接作用在調用的地方迅矛,誰調用退出誰妨猩。其他內聯(lián)函數(shù)中的return一樣(return也被復制到了調用內聯(lián)函數(shù)的函數(shù)體里)。如果要只退出lambda秽褒,可以使用return@xxx壶硅。
-
內聯(lián)屬性
對屬性來說,我們會有get销斟,set的方法來操作這個屬性庐椒。
get,set就是個函數(shù),我們可以標識他們?yōu)閮嚷?lián)函數(shù):val foo: Foo inline get() = Foo() var bar: Bar get() = ... inline set(v) { ... } // inline var bar: Bar get() = ... set(v) { ... }
-
實例化參數(shù)類型
有時我們需要訪問傳遞過來的類型蚂踊,把它作為參數(shù):
fun <T> TreeNode.findParentOfType(clazz: Class<T>): T? { var p = parent while (p != null && !clazz.isInstance(p)) { p = p?.parent } @suppress("UNCHECKED_CAST") return p as T } //調用 myTree.findParentOfType(javaClass<MyTreeNodeType>() ) // myTree.findParentOfType(MyTreeNodeType::class.java) //我們想要的僅僅是給這個函數(shù)傳遞一個類型约谈,如果即像下面這樣就很方便: myTree.findParentOfType<MyTreeNodeType>() //為了達到這個目的,內聯(lián)函數(shù)支持具體化的類型參數(shù)申明 reified inline fun <reified T> TreeNode.findParentOfType(): T? { var p = parent while (p != null && p !is T) { p = p?.parent } return p as T }
我們用 refied 修飾符檢查類型參數(shù)犁钟,既然它可以在函數(shù)內部訪問了棱诱,也就基本上接近普通函數(shù)了。因為函數(shù)是內聯(lián)的特纤,所以不許要反射军俊,像 !is `as`這樣的操作都可以使用。同時捧存,我們也可以像上面那樣調用它了 myTree.findParentOfType<MyTreeNodeType>() 粪躬。普通的函數(shù)(沒有標記為內聯(lián)的)不能有實例化參數(shù)。
在很多情況下會使用反射訪問類型數(shù)據(jù)昔穴,我們仍然可以使用實例化的類型參數(shù) javaClass() 來訪問它:
inline fun methodsOf<reified T>() = javaClass<T>().getMethods() fun main(s: Array<String>) { println(methodsOf<String>().joinToString('\n')) }
?
協(xié)程
镰官。。吗货。
空安全
-
Kotlin 類型系統(tǒng)致力于消滅空引用(NPE)泳唠,在 Kotlin 類型系統(tǒng)中可以為空和不可為空的引用是不同的,屬性默認是要賦初值的宙搬,不能為空:
var a: String ="abc" a = null //編譯錯誤 //允許為空笨腥,我們必須把它聲明為可空的變量 var b: String? = "abc" b = null //調用 a 的方法拓哺,而不用擔心 NPE 異常: val l = a.length() //使用 b 調用同樣的方法就可能報錯 val l = b.length() //錯誤:b 可能為空
-
使用可空屬性(?)時的四種方式:
-
在條件中檢查 null:
val l = if (b != null) b.length() else -1 //更復雜的條件: if (b != null && b.length() >0) print("Stirng of length ${b.length}") else print("Empty string")
-
安全調用,使用安全操作符,?.
b?.length() //如果 b 不為空則返回長度脖母,否則返回空士鸥。這個表達式的的類型是 Int?,安全調用在鏈式調用是是很有用的。比如谆级,如果 Bob 是一個雇員可能分配部門(也可能不分配)烤礁,如果我們想獲取 Bob 的部門名作為名字的前綴,就可以這樣做: bob?.department?.head?.name //這樣的調用鏈在任何一個屬性為空都會返回空
-
Elvis 操作符肥照,?:
val l = b.length()?: -1 //如if表達式: val l: Int = if (b != null) b.length() else -1 //如果 ?: 左邊表達式不為空則返回脚仔,否則返回右邊的表達式。注意右邊的表帶式只有在左邊表達式為空是才會執(zhí)行 //注意在 Kotlin 中 throw return 是表達式舆绎,所以它們也可以在 Elvis 操作符右邊鲤脏。這是非常有用的,比如檢查函數(shù)參數(shù)是否為空: fun foo(node: Node): String? { val parent = node.getParent() ?: return null val name = node.getName() ?: throw IllegalArgumentException("name expected") //... }
-
!! 操作符
NPE-lovers亿蒸,我們可以用 b!! 凑兰,這會返回一個非空的 b 或者拋出一個 b 為空的 NPE:
val l = b !!.length()
-
-
安全轉換
普通的轉換可能產生 ClassCastException 異常。另一個選擇就是使用安全轉換边锁,如果不成功就返回空:
val aInt: Int? = a as? Int
等式
-
在 kotlin 中有兩種相等
-
參照相等:參照相等是通過 === 操作符判斷的(不等是!== ) a===b 只有 a b 指向同一個對象是判別才成立姑食。另外,你可以使用內聯(lián)函數(shù) identityEquals() 判斷參照相等:
a.identityEquals(b) a identityEquals b
-
結構相等:結構相等是通過 == 判斷的茅坛。像 a == b 將會翻譯成:
a?.equals(b) ?: b === null //如果 a 不是 null 則調用 equals(Any?) 函數(shù)音半,否則檢查 b 是否參照等于 null //注意完全沒有必要為優(yōu)化你的代碼而將 a == null 寫成 a === null 編譯器會自動幫你做的
kotlin中==相當于java的equals,===相當于java的==
-
多重申明(解構申明)
var (name, age) = person
-
意思就是一次性申明多個變量贡蓖,并把=號右邊的對象的屬性拆箱出來賦值給變量曹鸠。如:
data class Person(var name: String, var age: Int) { } var person: Person = Person("Jone", 20) var (name, age) = person println("name: $name, age: $age")// 打印:name: Jone, age: 20
如果拆箱出對象的屬性:
val name = person.component1() val age = person.component2()
person.component1斥铺,component2怎么來的呢彻桃,Kotlin的數(shù)據(jù)類編譯器會根據(jù)主構造器中聲明的屬性, 自動推斷生成componentN() 函數(shù)群, 這些函數(shù)與類的屬性對應, 函數(shù)名中的數(shù)字1到N,與屬性的聲明順序一致。那么如果不是數(shù)據(jù)類就要自己編寫對象的componentN函數(shù):
class Person(val name: String, val age: Int) { operator fun component1(): String { return name } operator fun component2(): Int { return age } }
-
解構申明可以用在for循環(huán)中:
var personA: Person = Person("Door", 22, "ShanDong") var personB: Person = Person("Green", 30, "BeiJing") var personC: Person = Person("Dark", 23, "YunNan") var personD: Person = Person("Tool", 26, "GuanDong") var personE: Person = Person("Mark", 24, "TianJin") var pers = listOf(personA, personB, personC, personD, personE) for ((name, age) in pers) { println("name: $name, age: $age") }
-
Map使用結構申明晾蜘,Kotlin的標準庫中邻眷,對Map實現(xiàn)了這些擴展函數(shù):
operator fun <K, V> Map<K, V>.iterator(): Iterator<Map.Entry<K, V>> = entrySet().iterator() operator fun <K, V> Map.Entry<K, V>.component1() = getKey() operator fun <K, V> Map.Entry<K, V>.component2() = getValue()
所以在使用Map.Entry.getkey時使用調用到component1(),getvalue時調用component2():
var personA: Person = Person("Door", 22, "ShanDong")
var personB: Person = Person("Green", 30, "BeiJing")
var personC: Person = Person("Dark", 23, "YunNan")
var personD: Person = Person("Tool", 26, "GuanDong")
var personE: Person = Person("Mark", 24, "TianJin")
var map = HashMap<String, Person>()
map.put("1", personA)
map.put("2", personB)
map.put("3", personC)
map.put("4", personD)
map.put("5", personE)
for ((key, value) in map) {
println("key: $key, value: $value")
}
// Log打印
key: 1, value: Person(name='Door', age=22, addr='ShanDong', mobile=null)
key: 2, value: Person(name='Green', age=30, addr='BeiJing', mobile=null)
key: 3, value: Person(name='Dark', age=23, addr='YunNan', mobile=null)
key: 4, value: Person(name='Tool', age=26, addr='GuanDong', mobile=null)
key: 5, value: Person(name='Mark', age=24, addr='TianJin', mobile=null)
Ranges
-
表示從多少到多少剔交,可用于if判斷和for循環(huán)肆饶,與in關鍵字配合,常見用法:
// Checking if value of comparable is in range. Optimized for number primitives. if (i in 1..10) println(i) if (x !in 1.0..3.0) println(x) if (str in "island".."isle") println(str) // Iterating over arithmetical progression of numbers. Optimized for number primitives (as indexed for-loop in Java). for (i in 1..4) print(i) // prints "1234" for (i in 4..1) print(i) // prints nothing for (i in 4 downTo 1) print(i) // prints "4321" for (i in 1..4 step 2) print(i) // prints "13" for (i in (1..4).reversed()) print(i) // prints "4321" for (i in (1..4).reversed() step 2) print(i) // prints "42" for (i in 4 downTo 1 step 2) print(i) // prints "42" for (x in 1.0..2.0) print("$x ") // prints "1.0 2.0 " for (x in 1.0..2.0 step 0.3) print("$x ") // prints "1.0 1.3 1.6 1.9 " for (x in 2.0 downTo 1.0 step 0.3) print("$x ") // prints "2.0 1.7 1.4 1.1 " for (str in "island".."isle") println(str) // error: string range cannot be iterated over
原理參見標準庫中接口:Range 岖常,Progressiont和和操作函數(shù)的擴展驯镊。
-
for in :
如果你想通過 list 或者 array 的索引進行迭代,你可以這樣做:
for (i in array.indices) print(array[i]) //-------------------------// for ((index, value) in array.withIndex()) { println("the element at $index is $value") }
?
類型檢查和轉換
-
類型檢查:is !is 表達式:
//運行時檢查一個對象是否是某個特定類: if (obj is String) { print(obj.length) } if (obj !is String) { // same as !(obj is String) print("Not a String") } else { print(obj.length) } //智能轉換,編譯器會跟蹤 is 檢查靜態(tài)變量,并在需要的時候自動插入安全轉換: fun demo(x: Any) { if (x is String) { print(x.length) // x is automatically cast to String } } if (x !is String) return print(x.length) //x 自動轉換為 String //在 || && 操作符板惑,when 表達式和 whie 循環(huán)中: // x is automatically cast to string on the right-hand side of `||` if (x !is String || x.length == 0) return // x is automatically cast to string on the right-hand side of `&&` if (x is String && x.length > 0) print(x.length) // x is automatically cast to String when (x) { is Int -> print(x + 1) is String -> print(x.length + 1) is Array<Int> -> print(x.sum()) }
-
轉換:
用as 操作符來轉換類型:
val x: String = y as String //null 不能被轉換為 String 因為String不是 nullable橄镜,也就是說如果 y 是空的,則上面的代碼會拋出空異常冯乘。為了 java 的轉換語句匹配我們得像下面這樣: val x: String?= y as String?
"安全"轉換:
val x: String ?= y as? String //為了避免拋出異常蛉鹿,可以用 as? 這個安全轉換符,這樣失敗就會返回 null
This表達式
-
如果 this 沒有應用者往湿,則指向的是最內層的閉合范圍。為了在其它范圍中返回 this 惋戏,需要使用標簽:
//this@lable class A { // implicit label @A inner class B { // implicit label @B fun Int.foo() { // implicit label @foo val a = this@A // A's this val b = this@B // B's this val c = this // foo()'s receiver, an Int val c1 = this@foo // foo()'s receiver, an Int val funLit = @lambda {String.() -> val d = this // funLit's receiver val d1 = this@lambda // funLit's receiver } val funLit2 = { (s: String) -> // foo()'s receiver, since enclosing function literal // doesn't have any receiver val d1 = this } } } }
運算符號重載
领追。。响逢。
operator fun get(position: Int) = dailyForecast[position]
//xxx[position]
一些使用時的筆記(建議)
-
當需要把一個對象轉成另一個绒窑,或有多個當前類或對象的.調用等,可以使用這些擴展和函數(shù)舔亭,提高效率:
- let
- apply
- run
- with些膨,with是個函數(shù)
使用.isNullOrEmpty(),isNullOrBlank(),isBlank(),isEmpty(),isNotBlank(),isNotEmpty()來代替TextUtils判斷字符串。
善用集合中的各種擴展函數(shù)钦铺,如reduce,filter,map,any,all,count,max,sumBy等等订雾。
構造函數(shù)里的變量如果要在類中使用(類屬性)要標記定義關鍵字var或val,否則作用域不會是整個類矛洞,就像只是函數(shù)的參數(shù)一樣洼哎。
一些高階函數(shù),lambda {}里不要用return沼本,是返回的最后一行噩峦,如果用return他又是inline的話會返回了外層的函數(shù)。
利用默認參數(shù)減少(java)方法重載
可空也是一種類型(可空的xx類型)抽兆,可接受實參為空或具體類型的實例识补,可空類型的實例變量要解包(!!)后才可以使用原類型的屬性、方法辫红。
for( index in 5..1),其中5和1只能是數(shù)值凭涂,如果用變量要用(x-1)包起來并轉成數(shù)字:for (i in (x+1)..(y+1))。
-
kotlin 沒有Volatile等并發(fā)編程的關鍵字厉熟,這是kotlin有意為之导盅,kotlin讓為這應該讓函數(shù)庫來做,但并不是不能用揍瑟,可以使用@Volatile白翻,@Synchronized注解來使用相應功能,@Volatile標記jvm的備用字段為volatile。wait(), notify()等Object(在Kotlin的Any中沒有這些方法)的方法可以這么使用:
private val lock = java.lang.Object() fun produce() = synchronized(lock) { while (items >= maxItems) { lock.wait() } Thread.sleep(rand.nextInt(100).toLong()) items++ println("Produced, count is $items: ${Thread.currentThread()}") lock.notifyAll() } fun consume() = synchronized(lock) { while (items <= 0) { lock.wait() } Thread.sleep(rand.nextInt(100).toLong()) items-- println("Consumed, count is $items: ${Thread.currentThread()}") lock.notifyAll() }
-
當碰到用java時常用的如果不如為就…時可以用let等擴展:
if (data != null) { nameTv.setText(data.name); } //kotlin data?.apply { nameTv.text=name info{"xxx"} } data?.let{ nameTv.text=it.name } mOnActionListener?.onAction()
使用高階函數(shù)+函數(shù)對象定義監(jiān)聽器(java中的onClickListener等)
-
當可變屬性(var)定義為可空(?)時編譯器報錯:Smart cast to 'Type' is impossible, because 'variable' is a mutable property that could have been changed by this time 解決的幾種辦法:
var name: String? = null val names: ArrayList<String> = ArrayList() //1:如果能用只讀滤馍,改成val岛琼。 //2::用一個本地變量接收再使用: fun foo() { val nameLoc = a.name if(nameLoc != null) { names.add(name); } } //3:用?操作符 name?.let{ names.add(name); } //4:如果是在用Elvis操作符 foo1(name?:"") //循環(huán)中 names.add(name?:continue);
作者:竹塵居士
博客:http://zhuchenju.com