|
|
|
|
移动端
创建专栏

漫画:Kotlin 的扩展细节探究

扩展并不是 Kotlin 首创的,在 C# 和 Gosu 里,其实早就有类似的实现,Kotlin 本身在设计之初,就参考了很多语言的优点!

作者:张旸|2018-05-15 16:12

人工智能+区块链的发展趋势及应用调研报告


 

扩展

扩展并不是 Kotlin 首创的,在 C# 和 Gosu 里,其实早就有类似的实现,Kotlin 本身在设计之初,就参考了很多语言的优点!

Kotlin 可以利用扩展,在不继承父类也不使用任何装饰器设计模式的情况下,对指定的类进行功能的扩展。

Kotlin 的扩展包含了扩展函数和扩展属性,需要适用特殊的声明方式来完成。也就是说你可以对任何类,增加一些方法或者属性,来增强它的功能。

比较常见的场景,就是原本我们需要实现的各种 SpUtils、ViewUtils 之类的各种 XxxUtils 工具类。如果需要,我们可以直接在对应的类上,进行直接扩展。

说的这么厉害,举个实际的例子就可以说明一切了。我一般会在项目内建立一个 SpUtils 的帮助类,来帮我们快速的操作 SharePreferences。

  1. fun Context.getSpString(key:String):String{ 
  2.     val sp = getSharedPreferences("cxmy_sp",Context.MODE_PRIVATE) 
  3.     return sp.getString(key,""

在这个例子中,我们对 Context 类进行扩展,为了让它能够支持快速的从 SharePreferences 中获取到持久化的数据。当然,我们还是要传递进去一个我们存储数据的 Key。

这样使用它就非常的简单了,我们可以直接能够持有 Context 的地方,直接调用 getSpString() 方法。

  1. // Activity 中 
  2. getSpString("cxmy"
  3. // or 
  4. mContext.getSpString("cxmy"

扩展是静态解析的

我们知道,Kotlin 最终依然会被编译成 Java 的字节码在虚拟机中运行。Kotlin 也无法突破 Java 中不被允许的操作限制,所以它并不能真正的修改他们所扩展的类。

通过定义一个扩展,其实你并没有在一个现有类中,真的插入一个新的方法或者属性,仅仅是可以通过该类型的变量,用点表达式调用这个新方法或者属性。

类是允许继承的,而静态解析这一规则,就是为了在类的继承这一点上,不存在二义性。

当父类以及它的子类,都通过扩展的方式,增加一个 foo() 方法的时候,具体在调用的时候,是调用父类的 foo() 方法还是子类的 foo() 方法,完全取决于调用时,表达式所在的类型决定的,而不是由表达式运行时的类型决定的。

这里强调的扩展是静态解析的,即他们不是根据接受者类型的虚方法来判定调用那个方法。

一例胜千文,我们依然来举个例子。

  1. open class A() 
  2. class B:A(){ 
  3. fun A.foo(){ 
  4.     Log.i("cxmy","A.foo"
  5. fun B.foo(){ 
  6.     Log.i("cxmy","B.foo"
  7. fun printFoo(a: A){ 
  8.     a.foo() 
  9. printFoo(B()) 

在这个例子中,我们传递进去的是 B 对象,但是实际上会调用 A.foo() 方法,所以输出应该是 "A.foo()"。

这也印证了扩展是依据调用所在的表达式类型来决定的,而不是由表达式运行时的类型决定的。

在 Kotlin 中,使用 is 操作符,会让代码块中的类型有一次隐式转换,但是它对扩展是无效的,如果有特殊要求,可以使用 as 操作符显式的进行强转,方可生效。

  1. fun foo(){ 
  2.     val b = B() 
  3.     b.foo() 
  4.     if(b is A){ 
  5.       (b as A).foo() 
  6.       b.foo() 
  7.     } 

随手运行一下,它的结果就明朗了。

  1. B.foo() 
  2. A.foo() 
  3. B.foo() 

不过虽说静态解析这一规则是为了限制继承的歧义,但是正常使用扩展,它其实是可以在其继承者身上调用的。例如在 Context 类上扩展了某个方法,同样可以通过 Activity 或者 Server 这些 Context 的子类进行调用,它们并不冲突。

可空接收者

扩展的类的类型,也可以是一个可空的接收者类型。也就是我们可以在一个可空的类上定义扩展,大大的增加了扩展的适用范围。

  1. fun Any?.toString(): String { 
  2.     if (this == nullreturn "null" 
  3.     // 空检测之后,“this”会自动转换为非空类型,所以下面的 toString() 
  4.     // 解析为 Any 类的成员函数 
  5.     return toString() 

在这个例子中,我们在任意对象上,通过扩展实现了 toString() 方法,注意这里扩展的类是 Any? ,它是允许在一个为 null 的对象上直接调用的。

扩展属性

与函数类似,Kotlin 同样支持扩展属性。

  1. val Context.pgName: String 
  2.     get() = "com.cxmy.dev" 

和扩展方法一样,扩展属性不过扩展属性并不等于这个类上真实的属性,它并没有实际的将这个属性插入到这个类当中。

因此,对扩展属性来说,幕后字段 field 是不存在的,所以我们没法写类似这样的代码,并且扩展属性不能有初始化器。

  1. var stringRepresentation: String = "cxmyDev" 
  2.     get() = field.toString() 
  3.     set(value) { 
  4.         field = value // 解析字符串并赋值给其他属性 
  5.     } 

虽然扩展属性没有幕后字段,但是它们的行为我们依然可以通过显示提供的 getters/setters 来定义。

例如:

  1. var Context.channel: String 
  2.     get() { 
  3.         return getSpString("channel"
  4.     } 
  5.     set(value) { 
  6.         setSpString("channel",value) 
  7.     } 

虽然没有幕后字段 field ,但是我们可以将值存储在其他地方,这里举例将其存储在 SharePreferences 里。

在 Java 中调用 Kotlin 的扩展代码

首先,Kotlin 在设计之初,就已经考虑了和 Java 互相调用的问题,所以这一点我们完全不用担心,不知道怎么调用,只要去找对应的调用方法就好了。

例如文档中的例子:

在 org.foo.bar 包内的 example.kt 文件中声明的所有函数和属性,包括扩展函数,都会编译成一个名为 org.foo.bar.ExampleKt 的 Java 类的静态方法。

这也印证了前面提到的,对于 Kotlin 的扩展,它并不会真的在扩展类中,插入一个方法或者属性,而是以一个 XxxKt 的命名方式命名的类的形似存在。

而 Kotlin 的扩展,在转换为 Java 字节码的时候,会进行特殊处理,会自动生成另外一个方法签名。

例如:

  1. // SpUtils.kt 
  2. fun Context.getSpString(key: String): String { 
  3.     val sp = getSharedPreferences("cxmy_sp", Context.MODE_PRIVATE) 
  4.     return sp.getString(key""

会变成:

  1. // SpUtilsKt.java 
  2. public static final String getSpString(Context context,String key){ 
  3.   //... 

可以看到它帮我们生成的方法中,会将扩展依赖的类当成一个参数传递给这个静态方法。这样,我们在 Java 中的调用,就清晰了。

  1. SpUtilsKt.getSpString(context,"channel"

扩展属性也一样,会变成一个 getXxx() 的方法,就不再赘述了。

虽然 XxxKt 这个类是自动生成的,我们无需关心细节。如果对这个命名有特殊嗜好,其实可以通过 @JvmName 注释,修改生成的 Java 类的类名,需要注意的是 @JvmName 注释,需要加载 kt 文件的首行,前面不能有其他代码。

  1. @file:JvmName("SpHelper"
  2.  
  3. // ... 

这样,我们在 Java 代码中调用的时候,就脱离了 Kt 字段,更像是一个原本就用 Java 语言编写的方法了。

扩展到这里就完全清晰了,有的点都涉及到了。实际上 Google I/O 上发布的 AndroidX KTX,基本上就是依赖 Kotlin 的扩展功能实现的,还不了解 Android KTX 的可以戳这里。

扩展对于 Kotlin 的意义非凡,也确实能让我们编写的代码更清晰以及调用起来更方便。

【编辑推荐】

  1. TCP接入层的负载均衡、高可用、扩展性架构
  2. 如何在Spring Boot使用Dubbo Activate扩展点
  3. Google "招安"了Kotlin Kotlin初体验
  4. 高可用高性能可扩展的单号生成方案
  5. 对象的自治和行为的扩展与适配
【责任编辑:武晓燕 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢

热门职位+更多