美文网首页
Kotlin系列 - 函数与类相关细节小结(二)

Kotlin系列 - 函数与类相关细节小结(二)

作者: 未扬帆的小船 | 来源:发表于2019-12-22 23:42 被阅读0次

    Kotlin细节文章笔记整理更新进度:
    Kotlin系列 - 基础类型结构细节小结(一)

    1. 函数继承与实现、复写等

    • 父类需要open才可以被继承(kotlin默认为final)
    • 父类的方式、属性需要open才可以被覆写
    • 接口、接口方法、抽象类默认为open
    • 覆写父类(接口)成员需要override关键字
    • 注意继承类时,实际上是调用了父类的构造方法
    • 类只能单继承,接口可以多实现
    abstract class Person(open val age: Int){
        abstract fun work()
    }
    
    class MaNong(age: Int): Person(age){
        override val age: Int
            get() = 0
        override fun work() {
            println("我是码农,我在写代码")
        }
    }
    
    class Doctor(override val age: Int): Person(age){
        override fun work() {
            println("我是医生,我在给病人看病")
        }
    }
    
    fun main(args: Array<String>) {
        val person: Person = MaNong(23)
        person.work()
        println(person.age)
    
        val person2 : Person = Doctor(20)
        person2.work()
        println(person2.age)
    }
    -------------------------------------打印出来
    我是码农,我在写代码
    0
    我是医生,我在给病人看病
    20
    

    2. 接口代理(by

    接口方法实现交给代理类实现

    class Manager(diver:Driver): Driver by driver
    

    3. 接口方法冲突(实现多接口)

    • 接口方法可以有默认实现
    • 签名一致(方法名、参数等一致)且返回值相同的冲突
    • 实现类必须覆写冲突方法
    • super<[接口名]>.方法名(参数列表)
    abstract class A{
        open fun x(): Int = 5
    }
    
    interface B{
        fun x(): Int = 1
    }
    
    interface C{
        fun x(): Int = 0
    }
    
    class D(var y: Int = 0): A(), B, C{
    
        override fun x(): Int {
            println("call x(): Int in D")
            if(y > 0){
                return y
            }else if(y < -200){
                return super<C>.x()
            }else if(y < -100){
                return super<B>.x()
            }else{
                return super<A>.x()
            }
        }
    }
    

    4.类及其成员的可见性private protect interanl public

    protected:子类可见
    internal:模块内可见

    5. Object关键字

    • 只有一个实例的类
    • 不能自定义构造函数
    • 可以实现接口、继承父类
    • 本质上就是单例模式最基本的实现
    object TestKotlin
    ------------------------------转化为java代码
    public final class TestKotlin {
       public static final TestKotlin INSTANCE;
    
       private TestKotlin() {
       }
    
       static {
          TestKotlin var0 = new TestKotlin();
          INSTANCE = var0;
       }
    }
    

    6. companion object伴生对象、静态变量成员还有 kotlin包级成员

    • kotlin允许不在类下面写成员变量跟方法,称为包级别对象/函数
    • 每个类可以对应有一个伴生对象companion object
    • 伴生对象companion objectjavastatic静态方法/静态成员的效果类似相同。
    • kotlin中的静态成员考虑用包级函数、变量代替
    • JvmFieldJvmStatic@file:JvmName(自己自定义的类名)
    package com.demo.dan.voice
    
    val Str = "包级成员"
    fun packFun(): String {
        return "包级函数"
    }
    
    class TestKotlin {
        companion object {
            fun compainFun(): String {
                return "伴生对象方法"
            }
            var Str = "伴生对象成员"
        }
    }
    

    Koltinjava中调用

    //--------------在kotlin中调用
    fun main() {
        println(Str)//包级成员
        println(packFun())//包级函数
        TestKotlin.Str
        TestKotlin.compainFun()
    }
    
    //----------------在java中调用
    public class TestJava {
        public static void main(String[] args) {
            TestKotlin.Companion.compainFun();
            TestKotlin.Companion.getStr();
        }
    

    可以看到上面java调用kotlin的代码并不像我们java调用静态对象一样(中间多了层Companion)。
    上面的java调用Kotlin可以在伴生对象中增加对应的注解:
    函数上加 @JvmStatic
    变量上加@JvmField
    实现类似调用java静态成员/方法

    class TestKotlin {
        companion object {
          @JvmStatic
            fun compainFun(): String {
                return "伴生对象方法"
            }
            @JvmField
            var Str = "伴生对象成员"
        }
    }
    
    //----------------在java中调用
    public class TestJava {
        public static void main(String[] args) {
            TestKotlin.compainFun();
            String str = TestKotlin.Str;
        }
    }
    

    具体的大家可以转一下kotlinjava看一下加了注解跟没有加注解的区别,这里因为篇幅原因我就不加代码了。

    这里补充一点:
    java怎么调用kotlin的包级别对象呢???
    答案:可以使用@file:JvmName(自己自定义的类名)

    @file:JvmName("TestKotlin1")
    package com.demo.dan.imoc_voice
    
    val Str = "包级成员"
    fun packFun(): String {
        return "包级函数"
    }
    

    java调用:

    public class TestJava {
        public static void main(String[] args) {
            TestKotlin1.getStr();
            TestKotlin1.packFun();
        }
    }
    

    7. 方法重载跟默认参数

    • Overloads 方法重载
    • 名称相同、参数不同的方法
    • Jvm函数签名的概念:函数名、参数列表 (不包含返回值!),也就是当两个方法的方法名跟参数一致的时候,这个函数Jvm视为一个函数。
    • kotlin中可以为函数参数设置默认值,来实现重载。
    • 函数调用产生混淆得时候就使用具名函数
    //下面两个函数为同一函数签名,函数一样。
    fun a():Int{}
    fun a():String{}
    
    //方法重载
    fun a(s:Stirng){}
    fun a(s:String,i:int){}
    
    ------------------例子-------------------------------------------------------------
    //kotlin提供了默认函数,
    //所以上面的方法重载其实可以改成使用默认参数来实现
    fun a(s:String = "ss",i:Int){}
    
    fun main() {
        a(str ="str")
    }
    
    • java怎么调用kotlin的默认参数函数??
      答案:使用 @JvmOverloads注解
    //kolin代码----------
    @file:JvmName("TestKotlin1")
    package com.demo.dan.imoc_voice
    
    @JvmOverloads
    fun a(int:Int=1,str: String){}
    
    //java调用------------
    public class TestJava {
        public static void main(String[] args) {
            TestKotlin1.a("dfsdf");
        }
    }
    
    • java方法重载造成得bug ,例如List
    List.remove(int)
    List.remove(Object)
    

    当List里面传入得是int类型得数据,那么你remove()传入一个 4那么它是删除第四位数据还是删除4这个对象呢?这里它是默认调用了remove(int),而remove(Object)却不会被调用到。

    8. 扩展成员

    为现有类添加方法、属性

    • 扩展方法格式 fun X.y():Z{...}fun 被扩展类.自定义扩展方法名():返回类型{方法体 }
    • 扩展属性格式 var X.mvar 被扩展类.自定义扩展属性名
      注意扩展属性不能初始化,类似接口属性(X为被扩展类)
    • java调用扩展成员类似于调用静态方法
    @file:JvmName("TestKotlin1")
    package com.demo.dan.voice
    
    // 扩展String 增加multiply方法
    fun String.multiply(int: Int): String {
        val stringBuilder = StringBuilder()
        for (i in 0 until int) {
            stringBuilder.append(this)
        }
        return stringBuilder.toString()
    }
    //扩展String 增加运算符 - 
    operator fun String.minus(int: Int): String {
        return this.substring(0, length-int+1)
    }
    //扩展成员变量
    val String.a: String
        get() = "扩展成员变量"
    //扩展成员变量
    var String.b: Int
        set(value){}
        get() = 6
    

    kotlin调用

    fun main(args: Array<String>) {
        println("我是码农".multiply(3))
        println("我是码农" - 2)
        println("".a)
        println("".b)
    }
    ------------打印出来的Log--------------------------
    我是码农我是码农我是码农
    我是码
    扩展成员变量
    6
    

    java调用

        public static void main(String[] args) {
         String str =  TestKotlin1.minus("我是Java调用",2);
         String strpr = TestKotlin1.getA("");
         System.out.println(str);
         System.out.println(strpr);
        }
    ------------打印出来的Log--------------------------
    我是Java调
    扩展成员变量
    

    9. 属性代理

    • val delega1 by lazy { }
    • by解析
    public inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value
    

    可以看到by实际上是一个操作符,这里是Lazy扩展getValue方法。

    • lazy原理解析
    @file:kotlin.jvm.JvmName("LazyKt")
    //关键代码1
    public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
    
    private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
      //关键代码2
        private var initializer: (() -> T)? = initializer
    //关键代码3
        @Volatile private var _value: Any? = UNINITIALIZED_VALUE
        // final field is required to enable safe publication of constructed instance
        private val lock = lock ?: this
    
        override val value: T
            get() {
                val _v1 = _value
                //关键代码4
                if (_v1 !== UNINITIALIZED_VALUE) {
                    @Suppress("UNCHECKED_CAST")
                    return _v1 as T
                }
              //关键代码5
                return synchronized(lock) {
                    val _v2 = _value
                    if (_v2 !== UNINITIALIZED_VALUE) {
                        @Suppress("UNCHECKED_CAST") (_v2 as T)
                    } else {
                        val typedValue = initializer!!()
                        _value = typedValue
                        initializer = null
                        typedValue
                    }
                }
            }
    
        override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
    
        override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."
    
        private fun writeReplace(): Any = InitializedLazyImpl(value)
    }
    

    剖析一下 lazy
    关键代码1:将lazy(initializer: () -> T)后面的代码块传入到SynchronizedLazyImpl(initializer)
    关键代码2: 进入SynchronizedLazyImpl将传入的代码块赋值于initializer
    关键代码3:_value 最开始为未初始化。
    关键代码4:如果_value不为空则直接返回(不执行我们传入的lazy后面代码块内的方法)
    关键代码5:如果_value为空,做了一下同步的判断如果不为空则直接返回(不执行我们传入的lazy的方法),否则执行我们的传入的代码块initializer并拿返回值初始化_value

    每次我们调用的时候被lazy代理的对象的时候,实际上是调用了里面的valueget(),当第一次调用时就会跑我们val delega1 by lazy { 代码块}代码块中的代码,实例完里面的SynchronizedLazyImpl#_value后,第二次开始就是直接返回对象。保持只有一个对象实例存在。

    • 上面的是val属性的代理,lazy能代理var的属性吗?
      答案:不行。里面并没有实现setValue的方法。
    • 自定义实现代理
      下面我们举一下例子 自己来实现一个代理吧。
    class Delegates{
        val hello by lazy {
            "HelloWorld"
        }
        val hello2 by X()
      //因为X代理了
        var hello3 by X()
    }
    
    //实现了X这个代理,并实现了 setValue getValue
    class X{
        private var value: String? = null
    
        operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
            println("getValue: $thisRef -> ${property.name}")
            return value?: ""
        }
    
        operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String){
            println("setValue, $thisRef -> ${property.name} = $value")
            this.value = value
        }
    }
    fun main(args: Array<String>) {
        val delegates = Delegates()
        println(delegates.hello)
        println(delegates.hello2)
        println(delegates.hello3)
        delegates.hello3 = "value of hello3"
        println(delegates.hello3)
    }
    -------------打印出来Log
    HelloWorld
    getValue: com.demo.dan.imoc_voice.Delegates@7530d0a -> hello2
    
    getValue: com.demo.dan.imoc_voice.Delegates@7530d0a -> hello3
    
    setValue, com.demo.dan.imoc_voice.Delegates@7530d0a -> hello3 = value of hello3
    getValue: com.demo.dan.imoc_voice.Delegates@7530d0a -> hello3
    value of hello3
    
    image.png

    上面我们实现一个代理类,并实现了setValuegetValue两个方法。这样就可以代理var类型的成员变量。
    可以看到当我们调用的时候实际上是调用了代理类的setValuegetValue两个方法。

    10. 数据类data&&特殊写法&&解决Javabean的问题

    • 默认实现了copy、toString,还有componentN等方法
    • 直接替代javaBean存在问题(可能出现无法初始化,构造方法签名不对等问题),因为javaBean类不是final类型可被继承,且有一个无参数构造函数。

    定义一个data数据类

    data class TestKotlin(val name:String,val age:Int)
    

    看一下转换成java的实现

    public final class TestKotlin {
       @NotNull
       private final String name;
       private final int age;
    
       @NotNull
       public final String getName() {
          return this.name;
       }
    
       public final int getAge() {
          return this.age;
       }
    
       public TestKotlin(@NotNull String name, int age) {
          Intrinsics.checkParameterIsNotNull(name, "name");
          super();
          this.name = name;
          this.age = age;
       }
    
       @NotNull
       public final String component1() {
          return this.name;
       }
    
       public final int component2() {
          return this.age;
       }
    
       @NotNull
       public final TestKotlin copy(@NotNull String name, int age) {
          Intrinsics.checkParameterIsNotNull(name, "name");
          return new TestKotlin(name, age);
       }
    
       // $FF: synthetic method
       public static TestKotlin copy$default(TestKotlin var0, String var1, int var2, int var3, Object var4) {
          if ((var3 & 1) != 0) {
             var1 = var0.name;
          }
    
          if ((var3 & 2) != 0) {
             var2 = var0.age;
          }
    
          return var0.copy(var1, var2);
       }
    
       @NotNull
       public String toString() {
          return "TestKotlin(name=" + this.name + ", age=" + this.age + ")";
       }
    
       public int hashCode() {
          String var10000 = this.name;
          return (var10000 != null ? var10000.hashCode() : 0) * 31 + this.age;
       }
    
       public boolean equals(@Nullable Object var1) {
          if (this != var1) {
             if (var1 instanceof TestKotlin) {
                TestKotlin var2 = (TestKotlin)var1;
                if (Intrinsics.areEqual(this.name, var2.name) && this.age == var2.age) {
                   return true;
                }
             }
    
             return false;
          } else {
             return true;
          }
       }
    }
    
    1. 可以看到TestKotlin.javafinal类型的,并且构造函数没有无参的。
    2. 里面实现的component1,component2实际上就是成员变量。
    3. 默认实现了toStringhashCodeequals等方法。

    我们来调用一下 我们写的TestKotlin数据类

    fun main() {
        val t = TestKotlin("小丹", 24)
        println(t.component1())
        println(t.component2())
        val (name,age)=t
        println(name)
        println(age)
    }
    -----------打印出来的Log
    小丹
    24
    小丹
    24
    
    • val (name,age)=t 这种特殊写法就是data类型的对象拥有的。

    我们在迭代Array数组的时候也有这种写法:

    fun main(args:Array<String>) {
        for ((index,value)in args.withIndex()){
        }
    }
    -------------------看一下 withIndex 的实现
    public fun <T> Array<out T>.withIndex(): Iterable<IndexedValue<T>> {
        return IndexingIterable { iterator() }
    }
    ---------------------看一下 返回的IndexedValue类型
    public data class IndexedValue<out T>(public val index: Int, public val value: T)
    

    最终可以看到IndexedValue也是一样的data类型。

    • 处理替代javaBean存在问题可以使用,allOpennoArg插件,前者帮我们去掉类前的final后者生成无参构造函数

    使用方法:

    1. 在项目(Project)下的build.gradle
    buildscript {//构件工具
        ext.kotlin_version = '1.3.50'
        repositories {
            google()
            jcenter()
            mavenCentral()
        }
        dependencies {
            ......
            classpath "org.jetbrains.kotlin:kotlin-noarg:${kotlin_version}"
            classpath "org.jetbrains.kotlin:kotlin-allopen:${kotlin_version}"
        }
    }
    

    2.在对应的模块(module)下的build.gradle

    apply plugin:'kotlin-noarg'
    apply plugin:'kotlin-allopen'
    noArg{
        //com.demo.dan.annotations.PoKo 
        //PoKo这个类是你自己创建的,要自己新建个annotations目录  
        // 并在下面创建一个类,然后将路径写进来
        annotation("com.demo.dan.annotations.PoKo")
    }
    allOpen{
    // 跟上面一样
        annotation("com.demo.dan.annotations.PoKo")
    }
    
    1. 新建对应的包名跟文件...\annotatios\PoKo.kt
    annotation class PoKo
    
    1. data类的类名上面添加注解
    @PoKo
    data class TestKotlin(val name: String, val age: Int)
    
    1. 经过上面的注解操作,再rebuild一下 再转换成java代码
    public class TestKotlin {
     ................省略其他的代码
       public TestKotlin() {
       }
    }
    

    可以看到这里已经帮我们生成了无参构造函数跟去掉final修饰关键字。

    注意:因为注解是在编译期的时候生效的,也就是说我们在写代码的时候还是调用不了无参构造函数,但是可以通过反射获取到无参构造函数。

    11. 内部类&&匿名内部类

    • kotlin默认是静态内部类,非静态内部类需要使用inner关键字
    //静态内部类
    class TestKotlin {
        class Inner{
        }
    }
    //非静态内部类
    class TestKotlin1{
       inner class Inner1{ }
    }
    
    fun main() {
        val inner = TestKotlin.Inner()
        val inner1 = TestKotlin1().Inner1()
    }
    

    当内部类需要外部类的状态时,则可以使用非静态内部类,因为非静态内部类默认持有外部类的引用。如果不需要持有外部类的引用,则使用静态内部类。

    • @Outter的使用,当非静态内部类跟外部类有成员名字相同时,获取外部类的成员可以用this@外部类名.成员变量
    class TestKotlin1 {
        val a = "TestKotlin1-string"
        inner class Inner1 {
            val a = "Inner1-string"
            fun aInnerfun(): String {
                return a
            }
            fun aTestKotlinfun(): String {
                return this@TestKotlin1.a
            }
        }
    }
    
    • 匿名内部类(object)的使用
    ------------------kotlin的写法
        val view =View(context)
        view.onClickListener = object : View.OnClickListener{
            override fun onClick(v: View?) {
            }
        }
    ----------------java的写法
    View view = null;
    view.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
      }
    });
    

    不同于java ,Ktolinobject:还支持同时实现单继承多实现

    interface TestInterface{
        fun test()
    }
    view.onClickListener = object: TestInterface,View.OnClickListener {
            override fun test() {
                TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
            }
            override fun onClick(v: View?) {
            }
    }
    
    

    12. 枚举类

    • 实例有限(可数)的类,一个枚举成员对应一个本类的实例,因为成员是你写上去固定的数目,所以对应的实例也是固定的。
    enum class LogLevel{
    VERBOSE,DEBUG,INFO,WARN,ERROR
    }
    ---类似于下面的kotlin代码
    class LogLevel2 protected constructor{
      companion object{
        val VERBOSE = LogLevel2()
        val DEBUG= LogLevel2()
        val INFO= LogLevel2()
        val WARN= LogLevel2()
        val ERROR= LogLevel2()
    }
    }
    
    • 如果在kotlinenum中要定义方法,记得要用;将成员跟方法隔开
    enum class LogLevel(val id:Int){
    VERBOSE(0),DEBUG(1),INFO(2),WARN(3),ERROR(4) ;
    
    fun getTag():String{
        return "$id,$name"
    }
    }
    

    13. 密封类sealed class

    子类有限(可数)的类:子类只能在同一个文件中存在,所以其他的文件继承不了它,所以子类有限(可数)。

    • kotlin版本<v1.1,子类必须定义为密封类的内部类
    • koltin版本>=v1.1,子类只需要于密封类在同一个文件中
    • 用于保护类不被外部继承使用,其也是final。
    sealed class PlayerCmd {
        class Play(val url: String, val position: Long = 0): PlayerCmd()
        class Seek(val position: Long): PlayerCmd()
        object Pause: PlayerCmd()
        object Resume: PlayerCmd()
        object Stop: PlayerCmd()
    }
    

    相关文章

      网友评论

          本文标题:Kotlin系列 - 函数与类相关细节小结(二)

          本文链接:https://www.haomeiwen.com/subject/fbscnctx.html