Lonely patients

时光不回头,当下最重要。

Kotlin - 扩展

  • Kotlin 同 C# 和 Gosu 类似,能够扩展一个类的新功能而无需继承该类或使用像装饰者这样的任何类型的设计模式。 这通过叫做 扩展 的特殊声明完成。Kotlin 支持 扩展函数 和 扩展属性
  • 扩展是有作用域的,并不是声明之后,整个模块内通用,不同package中,需要通过import声明
    • import 扩展所在类包名.扩展所在类名.扩展属性或函数名
    • 一般IDE可以检测到,支持快捷键导入

1.扩展函数

  • 声明扩展函数需要用接收者类型也就是被扩展的类型来作为他的前缀,简单说就是:你想为哪个类扩展函数,前缀就是哪个类的类名

    • 涉及到泛型的类,需要指定泛型参数
    • 若有子类继承与MyClass,那么子类也可以使用父类的扩展函数
     /**
      * 为 ArrayList<String>扩展test函数
      * 对 ArrayList<其他类型>无效
      */
     fun ArrayList<String>.test(index: Int, element: String) {
         this[index] = element//这时,this代表ArrayList<String>类型的对象
     }
     /**
      * MyClass 自定义类
      */
     class MyClass {
     }
     
     /**
      * 为MyClass扩展一个函数
      */
     fun MyClass.function1(value: String) {
         println("$value 是${this.javaClass.name}的扩展函数")
     }
     
     fun main(a: Array<String>) {
         val arrayList = arrayListOf<String>("1", "2", "3")
         println(arrayList)//输出[1, 2, 3]
         arrayList.test(0, "3")//使用扩展函数,将第一个元素变成3
         println(arrayList)//输出[1, 3, 3]
     
         val myClass= MyClass()//自定义类
         myClass.function1("function1")//若有子类继承与MyClass,那么子类也可以使用父类的扩展函数
     }
    
  • 扩展是静态解析的

    • 扩展不能真正的修改所扩展的类,像上面例子中的ArrayList<String>.test()扩展函数,你并没有在扩展类中加入成员,仅仅是做了一个声明,所以使用扩展并不会修改扩展的类,只是通过声明告诉IDE这个类又加了一个函数罢了,可以通过该类型的变量用点表达式去调用这个新函数
    • 扩展函数是静态分发的,它们不是根据接收者类型的虚函数。 这意味着调用的扩展函数是由函数调用所在的表达式的类型来决定的, 而不是由表达式运行时求值结果决定的。下面用类成员函数和扩展函数做个比较:

      没有对比就没有伤害
        /**
         * 自定义类A
         */
        open class A {
            /**
             * 类成员函数
             * */
            open fun function2() {
                println("A.function2")
            }
        }
        
        /**
         * 自定义类B,继承于A
         */
        class B : A() {
            /**
             *  override
             */
            override fun function2() {
                println("B.function2")
            }
        }
        
        /**
         * 为类A扩展一个函数
         */
        fun A.function1() {
            println("A.function1")
        }
        
        /**
         * 为类B扩展一个函数
         */
        fun B.function1() {
            println("B.function1")
        }
        
        /**
         * 测试扩展函数,接收参数类型A
         * */
        fun function1Test(a: A) {
            a.function1()
        }
        
        /**
         * 测试类成员函数,接收参数类型A
         */
        fun function2Test(a: A) {
            a.function2()
        }
        
        fun main(args: Array<String>) {
            val a = A()
            val b = B()
    
            //测试类成员函数,b的超类也是A
            function2Test(a)//输出A.function2
            function2Test(b)//输出B.function2
            //测试扩展函数,同上
            function1Test(a)//输出A.function1
            function1Test(b)//输出A.function1
            /**
             * 通过以上两个测试可以看出:
             * 扩展函数和类成员函数 是由函数调用所在的表达式的类型来决定的, 而不是由表达式运行时求值结果决定的
             */
        }
    

    不知道以上对比方法,有没有什么不妥,如有不妥,请指正。

    • 如果一个类定义有一个成员函数(非private)和一个扩展函数,而这两个函数又有相同的接收者类型、相同的名字并且都适用给定的参数,这种情况总是取成员函数。 例如:
      class Fish {
          /**
           * 类成员函数
           * */
          fun leong() {
              println("1")
          }
          /**
           * 私有类成员函数,外部不可见
           * */
          private fun test() {
              println("1")
          }
      }
      
      /**
       * 为Fish类扩展一个和它成员函数有着相同名称、相同参数、相同返回值的函数
       */
      fun Fish.leong() {
          println("2")
      }
      /**
       * 为Fish类扩展一个和它成员函数有着相同名称、相同参数、相同返回值的函数
       */
      fun Fish.test() {
          println("2")
      }
      
      fun main(args: Array<String>) {
          val fish = Fish()
          fish.leong()//输出1,说明类成员函数与扩展函数出现重合时,调用该函数,以类成员函数优先
          fish.test()//输出2,因为类成员函数是private,类成员函数对外不可见
      }
      
  • 可空接收者
    注意可以为可空的接收者类型定义扩展。这样的扩展可以在对象变量上调用, 即使其值为 null,并且可以在函数体内检测 this == null,这能让你在没有检测 null 的时候调用 Kotlin 中的toString():检测发生在扩展函数的内部

      fun Any?.toString(): String {
          if (this == null) return "this is null"  //空检测之后,“this”会自动转换为非空类型,
          //所以下面的 toString()解析为 Any 类的成员函数,按ctrl进去看看
          return toString()
          //用到之前所学的知识空安全和“If not null and else 缩写 ?:”,
          //其实上面可以简写为:  return this?.toString()?:"this is null"
      }
      
      fun main(args: Array<String>) {
          val s: String? = null
          println(s.toString())
      }
    

2.扩展属性

  • 和函数类似,Kotlin 支持扩展属性
    • val Fish.name="Leong",这样错误的写法,由于扩展没有实际的将成员插入类中,因此对扩展属性来说幕后字段是无效的。这就是为什么扩展属性不能有初始化器。他们的行为只能由显式提供的 getters/setters 定义

      class Fish {
          private var name = "Leong"
      }
      
      val Fish.name: String get() = "Fish"
      val Fish.sex: String? get() = "男"//扩展一个性别属性
      
      fun main(args: Array<String>) {
          val fish = Fish()
          println(fish.name)//输出Fish,出现重合的情况,和上面讲到的扩展函数一样
          println(fish.sex)//输出男
      }
      
  • 伴生对象的扩展,和上面讲到的差不多:
   class Fish {
       //声明伴生对象
       companion object {}
   }
   
   /**
    * 为伴生对象扩展函数
    */
   fun Fish.Companion.test() {
       println("test")
   }
   
   /**
    * 为伴生对象扩展属性
    */
   val Fish.Companion.name get() = "fish"
   
   fun main(args: Array<String>) {
       Fish.test()
       println(Fish.name)
   }

3.扩展声明为成员

  • 在一个类内部你可以为另一个类声明扩展。在这样的扩展内部,有多个 隐式接收者 —— 其中的对象成员可以无需通过限定符访问。扩展声明所在的类的实例称为 分发接收者,扩展函数调用所在的接收者类型的实例称为 扩展接收者
  • 对于分发接收者和扩展接收者的成员名字冲突的情况,扩展接收者优先。要引用分发接收者的成员你可以使用限定的 this
    • 对于存在override的扩展函数时,this代表的含义不太一样,后面会讲到
      open class A {
          /**
           * A类的扩展函数
           *
           */
          open fun A.test() {
              println("3")//只是为了打印下调用顺序
              print()
          }
      
          open fun print() {
              println("4")
              println("this is A")
          }
      
          open fun print(a: A) {
              println("5")
              a.test()
          }
      
          fun printB(b: B) {
              println("5")
              b.test()
          }
      
      }
      
      class B : A() {
          /**
           * 虽然这个成员扩展函数可以override,但外部仍然无法调用它
           */
          override fun A.test() {
              println("6 - ${this.javaClass.name}")
              print()//这里的函数指的是A.print还是B.print?看下面测试结果
          }
      
          override fun print() {
              println("7")
              println("this is B")
          }
      
          override fun print(a: A) {
              println("1")
              a.test()
          }
      
      }
      
      fun main(args: Array<String>) {
          B().print(B())//测试一下,输出 1   6-B   7   this is B
          /**
           *  上面这行代码:
           *  首先,A类中声明了A.test扩展函数,并且可被子类override 或 在子类中通过子(超)类的实例调用
           *  那么B类继承于A类,B或A的实例可以在B中调用A.test函数
           *  但B类对A.test扩展函数进行了override,所以输出的是6而不是3,如 图片-1
           *  如果B类中不对A.test进行override,调用A.test肯定就会执行A类中的A.test函数
           * (你可以注释B类中override的扩展函数尝试一下)
           *  这就是前面所说的:这些被override的扩展函数的分发对于分发接收者类型是虚拟的
           *  然后入参类型是B,那么this的类型是B,调用的print函数当然是B类中的成员函数B.print()了
           *  所以这个输出结果没什么可意外的
           */
      
          B().print(A())//测试一下,输出 1   6-A   4   this is A
          /**
           *  上面这行代码:为什么输出的是6而不是3,和上面解释一样
           *  虽然代码在B类中,但入参类型是A,那this代表的类型是A
           *  所以调用的print函数,当然是A类中的成员函数A.print()了
           */
      
          A().print(B())//测试一下,输出 5  3  7  this is B
          /**
           * 前面说过,这些被override的扩展函数对于扩展接收者类型是静态的
           * 所以入参类型不论是A还是A的子类B,最后结果是一样的,静态解析到A类中的A.test扩展函数
           */
      
          A().printB(B())//测试一下,输出 5  3  7  this is B
          /**
           * 上面这个为毛也输出这个?
           * 因为类中成员扩展函数对外部不可见,所以在A类中,调用b.test其实是在调用A类中的test扩展函数,
           * A是B的超类,所以在此时,b被当作A类型来使用,如 图片-2
           */
      }
    

图片-1

图片-2

上文中有些理解也许比较片面或有不对的地方,望指正。

参考文档:
[1]扩展 – Kotlin 语言中文站

其他文章:
Kotlin – 入门基础
Kotlin – 类、继承、接口
Kotlin – 空安全

点赞