Kotlin的解析(下)

1,350 阅读7分钟

前言

  通过上章节的学习,了解到了枚举类,数据类,封闭类,泛型,扩展,对于一些使用的语法糖有了一定的认识,接下来让我们一起更加深入的了解一下更加晦涩的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", "override    verify")
        }
    })
}

  对象和类一样,只能有一个父类,但是可以实现多个接口

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,因此,添加在匿名对象的任何成员将无法访问

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", "override    verify")
        }
    })
}
1.1.4 访问封闭作用域内的变量

  在Kotlin中并没有静态成员的概念,🥚并不等于不能实现类似静态成员的功能,陪伴对象(Companion objects)就是Kotlin用来解决这个问题的语法糖   如果在Kotlin类中定义对象,那么就称这个对象为该类的陪伴对象,陪伴对象要使用companion关键字声明

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的基本要点,熟悉了这三节,对后面安卓编写会打下很好的基础,绝大部分代码都可以看懂,感谢大家细心的品尝,有不合适的地方,也请大家指教互相学习