Kotlin的解析(下)
通過上章節的學習,瞭解到了列舉類,資料類,封閉類,泛型,擴充套件,對於一些使用的語法糖有了一定的認識,接下來讓我們一起更加深入的瞭解一下更加晦澀的Kotline
1. 物件和委託
1.1 物件(可能有的又說,這麼簡單,莫急,你往後看看)
由於Kotlin沒有靜態成員的概念,因此Kotlin推出了一個有趣的語法糖:物件 (這個物件對於Kotlin來說,是一個關鍵字,而且這個欄位宣告的類,不需要實力化,感覺應該是Kotlin自己會new 出一個物件)
1.1.1 物件表示式
在Java中有一個匿名類概念,造建立的時候,無需指定類的名字
public class MyClass { public String name; public MyClass(String name) { this.name = name; } public void verify() { } } class Test { public static void process(MyClass myClass) { myClass.verify(); } public static void main(String[] args) { process(new MyClass("nii") { //初始化匿名類 @Override public void verify() { super.verify(); Log.i("tag", "Test verify"); } }); } } 複製程式碼
在Kotlin中,也有類似的功能,但不是匿名類,而是物件
open class MyClassDemo(name: String) { open var name = name open fun verify() { Log.i("tag", "verify") } } fun process(mycl: MyClassDemo) { mycl.verify() } fun main(arrgs: Array<String>) { //process 引數是一個物件,該物件是MyClassDemo匿名子類的例項,並且在物件中重寫verify函式 //要想建立一個物件,需要使用object關鍵字,該物件要繼承的需要與object之間用冒號(:)分隔 process(object : MyClassDemo("heihei") { override fun verify() { Log.i("tag", "overrideverify") } }) } 複製程式碼
物件和類一樣,只能有一個父類,但是可以實現多個介面
open class MyClassDemo(name: String) { open var name = name open fun verify() { Log.i("tag", "verify") } } interface MyInterface{ fun closeData(){ Log.i("tag","MyInterface") } fun hellow() } fun process(mycl: MyClassDemo) { mycl.verify() if (mycl is MyInterface){ mycl.closeData() } } fun main(arrgs: Array<String>) { //process 引數是一個物件,該物件是MyClassDemo匿名子類的例項,並且在物件中重寫verify函式 //要想建立一個物件,需要使用object關鍵字,該物件要繼承的需要與object之間用冒號(:)分隔 process(object : MyClassDemo("heihei"),MyInterface { override fun hellow() {//實現抽象的方法 } override fun verify() { Log.i("tag", "overrideverify") } }) } 複製程式碼
1.1.2 宣告匿名物件
匿名物件只能用在本地(函式)或者private宣告中,如果將匿名物件用於public函式的返回值,或public屬性型別,那麼Kotlin編譯器會將這些函式或者屬性的返回型別重新定義為匿名物件的父型別,如果匿名物件沒有實現任何介面,也沒有從任何類繼承,那麼父型別就是Any,因此,新增在匿名物件的任何成員將無法訪問
class TestDevin{ //private 函式,返回型別是匿名函式物件本身,可以訪問x private fun foo() =object { val x :String = "x" } //public函式,由於匿名物件沒有任何的父型別,因此函式返回型別是Any fun pubFoo() =object { val x:Int =1 } fun bar(){ var x1 = foo().x //可以訪問 var x2 =pubFoo().x//編譯錯誤,因為pubFoo是public方法,返回型別是Any } } 複製程式碼
1.1.3 訪問封閉作用域內的變數
Java的程式碼
public class MyClass { public String name; public MyClass(String name) { this.name = name; } public void verify() { } } class Test { public static void process(MyClass myClass) { myClass.verify(); } public static void main(String[] args) { final int n =20; process(new MyClass("nii") { //初始化匿名類 @Override public void verify() { int m = n; n=30; //編譯不通過 ,n的值不能修改 if (n==20){ } super.verify(); Log.i("tag", "Test verify"); } }); } } 複製程式碼
Kotlin的程式碼
在匿名物件中就可以任意訪問變數n,包括修改n的值
open class MyClassDemo(name: String) { open var name = name open fun verify() { Log.i("tag", "verify") } } interface MyInterface{ fun closeData(){ Log.i("tag","MyInterface") } fun hellow() } fun process(mycl: MyClassDemo) { mycl.verify() if (mycl is MyInterface){ mycl.closeData() } } fun main(arrgs: Array<String>) { var n :Int =20 process(object : MyClassDemo("heihei"),MyInterface { override fun hellow() {//實現抽象的方法 n=30 //可以修改 n的值 } override fun verify() { Log.i("tag", "overrideverify") } }) } 複製程式碼
1.1.4 訪問封閉作用域內的變數
在Kotlin中並沒有靜態成員的概念,並不等於不能實現類似靜態成員的功能,陪伴物件(Companion objects)就是Kotlin用來解決這個問題的語法糖 如果在Kotlin類中定義物件,那麼就稱這個物件為該類的陪伴物件,陪伴物件要使用companion關鍵字宣告
classBanSui{ companion object{ fun create():BanSui = BanSui() } } //陪伴物件定義的成員變數是可以直接通過類名訪問的 var create = BanSui.create() 複製程式碼
注意,雖然陪伴物件的成員看起來很像其他語言中的類的靜態成員,但在執行期間,這些成員仍然是真實物件的例項的成員,他們與靜態成員是不同的,不過使用@JvnStatic進行註釋,Kotlin編譯器會將其編譯成Byte code真正的靜態方法
2.1 委託
委託模式是軟體設計模式中的一項基本技巧。在委託模式中,有兩個物件參與處理同一個請求,接受請求的物件將請求委託給另一個物件來處理。Kotlin 直接支援委託模式,更加優雅,簡潔
2.1.1 類的委託
interface Base { fun print() } class BaseIml(val x: Int) : Base { override fun print() { Log.i("tag", "" + x) } } class Derived(b: Base) : Base by b { //Derived類使用關鍵字by將Base類的print函式委託給了一個物件 fun getHeName(): String { return "getHeName" } } fun goneDown() { var baseIml = BaseIml(20) Derived(baseIml).print() } 複製程式碼
2.1.2 委託屬性
Kotlin允許屬性委託,也就是將屬性的getter和setter函式的程式碼放到一個委託類中,如果在類中宣告屬性,只需要指定屬性委託類,這樣大大的減少程式碼的冗餘
class Delegate { operator fun getValue(thisRef: Any?, property: KProperty<*>): String { return "$thisRef, 這裡委託了 ${property.name} 屬性" } operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) { Log.i("tag","$thisRef 的 ${property.name} 屬性賦值為 $value") } } class Example { var p: String by Delegate() } class Example1{ var p1 :String by Delegate() } 複製程式碼
2.1.3委託類的初始化函式
如果委託類有主構造器,也可以向主構造器傳入一個初始化函式,這時,可以定義一個委託函式的返回值是委託類,並在委託的時候指定初始化函式。
class Delegate<T>(initializer:() ->T){ var name :String ="" var className = initializer() operator fun getValue(thisRef:Any?,property:KProperty<*>): String { Log.d("tag","$className.get 已經被呼叫") return name } operator fun setValue(thisRef:Any?,property:KProperty<*>,value :String){ Log.d("tag","$className.set 已經被呼叫") name =value } } public fun <T>delegate(initializer: () -> T):Delegate<T> = Delegate(initializer) class MyClass1{ var name :String by Delegate { Log.d("tag","MyClass1.name初始化函式呼叫" ) "《MyClass1》" } } class MyClass2{ var name :String by Delegate { Log.d("tag","MyClass2.name初始化函式呼叫" ) "《MyClass2》" } } var c1 = MyClass1() var c2 = MyClass2() c1.name ="Bill" c2.name ="Mike" Log.d("tag",c1.name) Log.d("tag",c2.name) 輸出: MyClass1.name初始化函式呼叫 MyClass2.name初始化函式呼叫 heihei.set 已經被呼叫 shazizi.set 已經被呼叫 heihei.get 已經被呼叫 Bill shazizi.get 已經被呼叫 Mike 複製程式碼
上面這段程式碼,為委託類加了一個主構造器,並傳入一個初始化函式,初始化函式的返回String型別的值
3.1 標準委託
3.1.1 惰性裝載
lazy是一個函式,實現惰性載入屬性,第一次呼叫get()時,將會執行從lazy函式傳入的Lambda表示式,然後會記住這次執行的結果,之後所有對get()的呼叫都只會簡單地返回以前記住的結果
val lazyValue: String by lazy { //LazyThreadSafetyMode.PUBLICATION Log.d("tag", "我是懶載入初始化...") "hellow" } Log.d("tag", lazyValue) Log.d("tag", lazyValue) 輸出: 我是懶載入初始化... hellow hellow 複製程式碼
預設的情況下,惰性載入屬性的執行時同步,屬性值只會在唯一一個執行緒內執行,然後所有的執行緒得到同樣的屬性值。如果委託屬性初始化函式不需要同步,多個執行緒可以同事執行初始化函式,那麼可以向lazy函式傳入一個LazyThreadSafetyMode.PUBLICATION引數。相反,如果你確信初始化函式值可能在一個執行緒執行,那麼可以使用LazyThreadSafetyMode.NONE模式
3.1.2可觀察屬性
就是當屬性變化時可以攔截其變化。實現觀察屬性值變化的委託函式Delegates.observable。
class UserDemo { //XIXI時name的屬性初始值 var name :String by Delegates.observable("XIXI"){ property, oldValue, newValue -> Log.d("tag","屬性:$property舊值: $oldValue新值:$newValue") } } var userDemo = UserDemo() userDemo.name = "Bill" userDemo.name="Devin" 輸出: 屬性:property name (Kotlin reflection is not available)舊值: XIXI新值:Bill 屬性:property name (Kotlin reflection is not available)舊值: Bill新值:Devin 複製程式碼
3.1.3 阻止屬性的賦值操作
如果你希望能夠攔截屬性的賦值操作,並且能夠“否決”賦值操作,那麼不要使用observable函式,而應該使用vetoable函式
class UserDemo2 { var name :String by Delegates.vetoable("XIXI"){ property, oldValue, newValue -> Log.d("tag","屬性:$property舊值: $oldValue新值:$newValue") var result = true; if (newValue.equals("Mary")){ result =false Log.d("tag","name的屬性值不能為Mary") } result //返回true 或false,表示允許或者否姐屬性的賦值 } } 輸出: 屬性:property name (Kotlin reflection is not available)舊值: XIXI新值:Devin Devin 屬性:property name (Kotlin reflection is not available)舊值: Devin新值:Mary name的屬性值不能為Mary Devin 複製程式碼
3.1.4 Map的委託
有一種常見的使用場景時將Map中的key-value映像到物件的同名屬性中
class UserDemo3(var map:Map<String,Any>){ val name :String by map //將map用作name屬性的委託 val love :String by map val sex :Int by map } var map = mapOf("name" to "李雪","love" to "打球" ,"sex" to 2) //map中key-value直接對映到UserDemo3類的屬性上,key的名稱要和屬性名字一樣,不然就對映不到 var userDemo3 = UserDemo3(map) Log.d("tag",userDemo3.name) Log.d("tag",userDemo3.love) Log.d("tag",userDemo3.sex.toString()) 輸出: 李雪 打球 2 複製程式碼
上面,UserDemo3使用val宣告屬性,這就意味這兩個舒屬性值時不可以修改,因為Map只有getValue方法,沒有setValue方法,所以Map只能通過UserDemo3物件讀取name等值,而不能通過UserDemo3設定屬性值,但是可不可以射呢?
3.1.5 MutableMap委託
class UserDemo4(var map:MutableMap<String,Any>){ var name :String by map //將map用作name屬性的委託 var sex :Int by map } var map = mutableMapOf("name" to "小明","sex" to 30) var userDemo4 = UserDemo4(map) Log.d("tag",userDemo4.name) Log.d("tag",userDemo4.sex.toString()) userDemo4.name = "小紅"//修改類中的值,map中的值也會跟著變 Log.d("tag",userDemo4.name) Log.d("tag","map"+map["name"]) map.put("sex",80)//修改map中的值,類中的也跟著變 Log.d("tag",userDemo4.sex.toString()) 輸出: 小明30 小紅 map小紅 80 複製程式碼
可以看出上面的修改都是雙向的,進行委託的時候
3. 高階函式於Lambda表示式
3.1 高階函式
高階函式是一種特殊的函式,它接受函式作為引數,或者返回一個函式。
interface Product { var area: String fun sell(name: String) } class MobilePhone : Product { override var area: String = "" override fun sell(name: String) { } } fun mobilePhoneArea(name: String): String { return "$name美國!" } fun processProduct(product: Product, area: (name: String) -> String): Product { product.area = area("HUAWEI") return product } var mobilePhone = MobilePhone() //將函式作為引數傳入高階函式,需要在函式前面加兩個(::)作為標記 processProduct(mobilePhone, ::mobilePhoneArea) Log.d("tag",mobilePhone.area) 輸出:tag: HUAWEI美國! //Lambda的表示式,將值傳入processProduct函式 processProduct(mobilePhone,{name -> "$name 美國"}) //Lambda表示式還提供了另一個表示式,如果Lambda表示式是函式的最後一個引數,可以將大括號寫在外面 processProduct(mobilePhone){ name -> "$name 美國" } 複製程式碼
3.2 Lambda表示式與匿名函式
Lambda表示式,又稱匿名函式,是一種“函式字面值”,也就是一盒沒有宣告的函式,但是可以作為表示式傳遞出去
3.2.1 函式型別
對於接受另一個函式作為自己引數的函式,必須針對這個引數指定一個函式型別
fun <T> max(coll: Collection<T>, less: (T, T) -> Boolean): T? { var max: T? = null for (it in coll) { if (max == null || less(max, it)) { //引數less的型別是(T,T)->Boolean,它是一個函式,接受兩個T型別引數,並且返回一個Boolean型別結果 max = it } } return max } fun compare(a: String, b: String): Boolean = a.length < b.length var list = listOf("dfsf", "sfsfdsf", "sdfsfff", "sfsfff") var max = max(list, { a, b -> a.length < b.length }) Log.d("tag", "max的值:" + max) var max2 = max(list, ::compare) Log.d("tag", "max2的值:" + max2) 複製程式碼
3.2.2Lambda表示式的語法
Lambda表示式包含在大括號之內,在完整語法形式中,引數宣告在小括號之內,引數型別宣告可選,函式體在“->”符號之後。如果表示式自動推斷的返回值型別不是Unit,那麼表示式函式體中,最後一條表示式的值被當作整個Lambda表示式的返回值
val sum = {x:Int, y:Int ->x+y} 複製程式碼
在很多的情況下,Lambda表示式只有唯一一個引數,如果Kotlin能夠自行判斷出Lambda表示式的引數定義,那麼它將允許我們省略唯一一個引數的定義(“->”也可以省略),並且會為我們隱含地定義這個引數,使用引數名為it
processProduct(product){ "${it}美國" } 複製程式碼
3.2.3 匿名函式
上面講到的Lambda表示式語法,還遺漏一點,就是可以指定函式的返回值型別,大多數情況下,不需要指定函式型別,因為可以自動推斷得到,但是的確需要明確指定返回值函式,可以選擇另種語法:匿名函式
fun (x:Int, y:Int):Int{ return x+y } 複製程式碼
引數和返回值型別的宣告與通常的函式一樣,但如果引數型別可以通過上下文推斷得到,那麼型別是可以省略;如果函式體是多條語句組成的程式碼段,則返回值型別必須明確指定(否則認為是Unit)
ints.filter(fun(item) = item >0) 複製程式碼
注意:匿名函式引數一定要在小括號內傳遞,允許將函式型別引數寫在小括號之外語法,僅對Lambda表示式有效
4. 函式
4.1 函式用法
函式必須使用fun關鍵字開頭,後面緊跟函式名字,以及一對小括號,如果有返回值,則需要在小括號後面加上(:),冒號後面是返回值型別
4.1.1 使用中綴標記法呼叫函式
Kotlin允許使用中綴表示式呼叫函式;所謂的中綴表示式,就是指將函式名稱放到兩個運算元中間,左側是包含函式物件,右側是函式的引數值,要滿足3個條件: (1)成員函式或者擴充套件函式 (2)只有1個引數 (3)使用infix關鍵字宣告函式
infix fun String.div(str :String):String{ return this.replace(str,"") } var str ="helloworld" Log.d("tag",str.div("l")) //中綴表示式 Log.d("tag",str div "l") //中綴表示式可以連續使用 Log.d("tag",str div "l" div "o") 複製程式碼
4.1.2 單表示式函式
如果函式的函式體只有一條語句,而且是Return語句,哪個可以省略函式體的大括號,以及return關鍵字。
fun double(x :Int):Int = x*2 //如果Kotlin編譯器可以推斷等號右側表示式的型別,那麼可以省略函式的返回值型別 fun double(x:Int)=x*2 複製程式碼
4.2 函式引數和返回值
4.2.1 可變引數
fun <T> asListDemo(vararg ts :T) :List<T>{ //vararg使用這個關鍵字定義 val result = ArrayList<T>(); for (t in ts){ result.add(t) } returnresult } var asListDemo = asListDemo(1, 2, 3, "a", 4, 5) Log.d("tag",asListDemo.toString()) 輸出: tag: [1, 2, 3, a, 4, 5] fun <T> asListDemo(vararg ts :T,value1: Int ,value2: String) :List<T>{ val result = ArrayList<T>(); for (t in ts){ result.add(t) } Log.d("tag",value1.toString()+value2) returnresult } var asListDemo = asListDemo(1, 2, 3, "a", 4, 5,value1 = 3,value2 = "xixi") Log.d("tag",asListDemo.toString()) //要傳遞陣列的話,再前面加上一個*的符號 val a = arrayOf(1,2,3) var asListDemo = asListDemo(-1, 2, *a, 4) Log.d("tag",asListDemo.toString()) 複製程式碼
4.2.2 返回值型別
如果函式體為多行語句組成的程式碼段,那麼就必須明確指定返回值型別,除非這個函式打算返回Unit,這時返回型別的宣告可以省略
4.2.3 區域性函式
在Kotlin中,函式可以定義在原始碼的頂級範圍內,這就意味著不像Java那樣,建立一個類來容納這個函式,除了頂級函式之外,Kotlin中的函式還可以定義為區域性函式、成員函式以及擴充套件函式。
fun saveFile() { //區域性函式 fun getFileName(fn: String): String { return "/user/$fn" } var filename = getFileName("test.txt") Log.d("tag", "$filename 已經儲存成功!") } 複製程式碼