Kotlin的解析(下)

前言

  經過上章節的學習,瞭解到了枚舉類,數據類,封閉類,泛型,擴展,對於一些使用的語法糖有了必定的認識,接下來讓咱們一塊兒更加深刻的瞭解一下更加晦澀的Kotlinehtml

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中,也有相似的功能,但不是匿名類,而是對象bash

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", "override verify")
        }
    })
}
複製代碼

  對象和類同樣,只能有一個父類,可是能夠實現多個接口less

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", "override verify")
        }
    })
}
複製代碼
1.1.2 聲明匿名對象

  匿名對象只能用在本地(函數)或者private聲明中,若是將匿名對象用於public函數的返回值,或public屬性類型,那麼Kotlin編譯器會將這些函數或者屬性的返回類型從新定義爲匿名對象的父類型,若是匿名對象沒有實現任何接口,也沒有從任何類繼承,那麼父類型就是Any,所以,添加在匿名對象的任何成員將沒法訪問ide

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的值ui

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", "override verify")
        }
    })
}
複製代碼
1.1.4 訪問封閉做用域內的變量

  在Kotlin中並無靜態成員的概念,🥚並不等於不能實現相似靜態成員的功能,陪伴對象(Companion objects)就是Kotlin用來解決這個問題的語法糖   若是在Kotlin類中定義對象,那麼就稱這個對象爲該類的陪伴對象,陪伴對象要使用companion關鍵字聲明this

class  BanSui{
    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 //返回truefalse,表示容許或者否姐屬性的賦值
    }
}

輸出:

屬性:    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.2 Lambda表達式的語法

  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 ="hello world"
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)
    }
    return  result
}

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)
    return  result
}

 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 已經保存成功!")
    }

複製代碼
4.2.4 泛型函數
fun <T> singList(item: T): T {
       //...
    }
複製代碼

總結

  經過本章節的學習,認識瞭解了對象、委託、高階函數、函數等的詳細講解,估計小白的咱們也差很少得到了知識,從上、中、下篇,已經講完了Kotlin的基本要點,熟悉了這三節,對後面安卓編寫會打下很好的基礎,絕大部分代碼均可以看懂,感謝你們細心的品嚐,有不合適的地方,也請你們指教互相學習

相關文章
相關標籤/搜索