【Koltlin】深入剖析Kotlin 扩展函数和扩展属性

背景

在kotlin中经常看到这样的if语句:

//s是一个String类型的对象
if(s.isNullOrEmpty()){
//执行空对象或空字符串的逻辑
}

笔者不禁想问,如果s是空对象,调用它的函数不会抛出空指针吗?

它是如何检查被调用的对象s是空对象的呢?

查阅资料后,发现原来这是kotlin的扩展函数的特性:

被扩展的类后面添加?.再接扩展函数名即表示可以在空对象上扩展。在扩展函数内, 可以通过 this 来判断接收者是否为 NULL,这样,即使接收者为 NULL,也可以调用扩展函数。例如:

fun Any?.toString(): String {
    if (this == null) return "null"
    // 空检测之后,“this”会自动转换为非空类型,所以下面的 toString()
    // 解析为 Any 类的成员函数
    return toString()
}
fun main(arg:Array<String>){
    var t = null
    println(t.toString())
}

看一下isNulOrEmpty函数的实现:

@kotlin.internal.InlineOnly
public inline fun CharSequence?.isNullOrEmpty(): Boolean {
    contract {
        returns(false) implies (this@isNullOrEmpty != null)
    }

    return this == null || this.length == 0
}

可以看到,当被调用对象是NULL时它返回true。并且返回类型Boolean是一个非空对象,所以isNullOrEmpty函数无论如何要么返回true要么返回false不可能是NULL。

扩展在kotlin的应用无处不在,像letalsolifelycleScopeviewModelScope等等都是扩展。

扩展这么神奇,那它到底是何物呢?从kotlin官方文档来看,也看不出个究竟:
https://book.kotlincn.net/text/extensions.html

官方文档只说了它的特点,并没有深入介绍:

扩展是静态解析的
扩展不能真正的修改他们所扩展的类。通过定义一个扩展,并没有在一个类中插入新成员, 只不过是可以通过该类型的变量用点表达式去调用这个新函数。

笔者动手写了一个demo,从字节码指令窥探一下它的原理。

Kotlin扩展函数和扩展属性的原理

com.devnn.bean目录下新建一个名为MyExtension.kt的文件,内容如下:

package com.devnn.bean

import com.devnn.demo.MainActivity

//扩展属性
val MainActivity.myExtProperty: String
    get() {
        val data: String? = this.getData()
        return "#${data}#"
    }
    
//扩展函数
fun MainActivity.myExtFun(): String {
    return "MainActivityExtFun:${this.javaClass.simpleName}"
}

上面的扩展属性访问了MainActivitygetData方法:

//com.devnn.demo.MainActivity
 fun getData():String{
        return "dataFromMainActivity"
 }

MainActivity中去访问这个扩展属性和扩展函数:

//com.devnn.demo.MainActivity
  override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
	    Log.i("MainActivity", "onCreate")
        Log.i("MainActivity", "myExtensionProperty=${myExtProperty}")
        Log.i("MainActivity", "myExtensionFun=${myExtFun()}")
}

运行打印日志如下:

2022-11-07 22:53:49.431 2737-2737/com.devnn.demo I/MainActivity: onCreate
2022-11-07 22:53:49.432 2737-2737/com.devnn.demo I/MainActivity: myExtensionProperty=#dataFromMainActivity#
2022-11-07 22:53:49.432 2737-2737/com.devnn.demo I/MainActivity: myExtensionFun=MainActivityExtFun:MainActivity

在build目录中查看MyExtension.kt被编译成了一个class文件,文件名是MyExtensionKt.class,查看它的字节码内容如下:

// class version 52.0 (52)
// access flags 0x31
public final class com/devnn/bean/MyExtensionKt {

  // compiled from: MyExtension.kt

  @Lkotlin/Metadata;(mv={1, 6, 0}, k=2, xi=48, d1={"\u0000\u000e\n\u0000\n\u0002\u0010\u000e\n\u0002\u0018\u0002\n\u0002\u0008\u0004\u001a\n\u0010\u0005\u001a\u00020\u0001*\u00020\u0002\"\u0015\u0010\u0000\u001a\u00020\u0001*\u00020\u00028F\u00a2\u0006\u0006\u001a\u0004\u0008\u0003\u0010\u0004\u00a8\u0006\u0006"}, d2={"myExtProperty", "", "Lcom/devnn/demo/MainActivity;", "getMyExtProperty", "(Lcom/devnn/demo/MainActivity;)Ljava/lang/String;", "myExtFun", "app_debug"})

  // access flags 0x19
  public final static getMyExtProperty(Lcom/devnn/demo/MainActivity;)Ljava/lang/String;
  @Lorg/jetbrains/annotations/NotNull;() // invisible
    // annotable parameter count: 1 (invisible)
    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
   L0
    ALOAD 0
    LDC "<this>"
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter (Ljava/lang/Object;Ljava/lang/String;)V
   L1
    LINENUMBER 8 L1
    ALOAD 0
    INVOKEVIRTUAL com/devnn/demo/MainActivity.getData ()Ljava/lang/String;
    ASTORE 1
   L2
    LINENUMBER 9 L2
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    BIPUSH 35
    INVOKEVIRTUAL java/lang/StringBuilder.append (C)Ljava/lang/StringBuilder;
    ALOAD 1
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/Object;)Ljava/lang/StringBuilder;
    BIPUSH 35
    INVOKEVIRTUAL java/lang/StringBuilder.append (C)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    ARETURN
   L3
    LOCALVARIABLE data Ljava/lang/String; L2 L3 1
    LOCALVARIABLE $this$myExtProperty Lcom/devnn/demo/MainActivity; L0 L3 0
    MAXSTACK = 2
    MAXLOCALS = 2

  // access flags 0x19
  public final static myExtFun(Lcom/devnn/demo/MainActivity;)Ljava/lang/String;
  @Lorg/jetbrains/annotations/NotNull;() // invisible
    // annotable parameter count: 1 (invisible)
    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
   L0
    ALOAD 0
    LDC "<this>"
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter (Ljava/lang/Object;Ljava/lang/String;)V
   L1
    LINENUMBER 13 L1
    LDC "MainActivityExtFun:"
    ALOAD 0
    INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class;
    INVOKEVIRTUAL java/lang/Class.getSimpleName ()Ljava/lang/String;
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.stringPlus (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/String;
    ARETURN
   L2
    LOCALVARIABLE $this$myExtFun Lcom/devnn/demo/MainActivity; L0 L2 0
    MAXSTACK = 2
    MAXLOCALS = 1
}

可以看到kotlin的扩展属性和扩展函数其实是类的静态方法。
被扩展类是这个静态方法的第一个实参。

这么一看,就明白了。说到底,它们只是kotlin的一个语法糖而已。

这么说扩展要访问被扩展类的方法,只能访问public类型的成员?经验证,笔者将MainActivitygetData访问改成private编译就会报错。

再看看MainActivity中的调用扩展的字节码:

 L2
    LINENUMBER 77 L2
    LDC "MainActivity"
    LDC "myExtensionProperty="
    ALOAD 0
    INVOKESTATIC com/devnn/bean/MyExtensionKt.getMyExtProperty (Lcom/devnn/demo/MainActivity;)Ljava/lang/String;
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.stringPlus (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/String;
    INVOKESTATIC android/util/Log.i (Ljava/lang/String;Ljava/lang/String;)I
    POP
   L3
    LINENUMBER 78 L3
    LDC "MainActivity"
    LDC "myExtensionFun="
    ALOAD 0
    INVOKESTATIC com/devnn/bean/MyExtensionKt.myExtFun (Lcom/devnn/demo/MainActivity;)Ljava/lang/String;
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.stringPlus (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/String;
    INVOKESTATIC android/util/Log.i (Ljava/lang/String;Ljava/lang/String;)I
    POP

观察调用方的字节码会发现调用扩展属性或者扩展方法,其实都是在调用静态方法,将this(被扩展对象)传给了这个静态方法作为实参,这样扩展就可以访问被扩展类中的成员。

关于扩展就介绍到这了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值