Kotlin中reified理解和实战应用

⏰ : 全文字数:1107+

🥅 : 内容关键字:kotlin, reified

Kotlin提供了一个reified关键字,这个关键字在实际项目中有很大的用处,比如泛型相关的封装,SDK封装等。今天我们就来学习下这块的知识。首先需要先了解下内联函数,也就是inline关键字

内联函数(inline)

在Kotlin中内联函数就是用关键字inline修饰的函数,它有什么作用呢?我看下面一段代码

kotlin 复制代码
fun main() {
    getUserInfo {
        println("get user info.")
    }
}

inline fun getUserInfo(block: () -> Unit): String {
    println("start to invoke getUserInfo method.")
    block.invoke()
    return "UserInfo"
}

很简单的一段代码,编译成apk后,我们可以看下它对应Class文件,代码如下:

java 复制代码
public final class Main {
    public static final void main() {
        // 调用处,不在是调用函数,而是代码块
        System.out.println((Object) "start to invoke getUserInfo method.");
        System.out.println((Object) "get user info.");
    }

    public static final String getUserInfo(Functions<Unit> block) {
        Intrinsics.checkNotNullParameter(block, "block");
        System.out.println((Object) "start to invoke getUserInfo method.");
        block.invoke();
        return "UserInfo";
    }
}

从上面的反编译的代码可以看到:

  • 在调用内联函数的地方,不再是一个函数,而是函数内的代码块
  • 而原来单独的getUserInfo函数还是存在,且函数参数编译成了对应的FunctionN对象

上从面可以知道,内联函数在被调用时,并不是分配栈调用函数,而是在编译时,把内联函数中的代码块替换到调用处。

优点

为什么会引入inline的概念呢?主要的原因我个人认为有两个:

  • 使用inline特性,性能上会有所提升,因为减少了调用函数时的内存,函数参数对象的创建等
  • 因为Kotlin支持高阶函数,存在大量的匿名函数,lambda,使用inline可以减少中间类的创建

注意:如果内联函数的代码块很多,那么有可能导致调用处的函数代码量很大。

reified

说这个之前,我先看下在Java中,如何实例化一个泛型对象?我们唯一的办法是像下面这样:

java 复制代码
    public static <T> T newInstance(Class<T> tClass) {
        try {
            return tClass.newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        } 
    }

只能通过传递Class的方式来创建,是因为存在泛型擦除的机制,无法知道T到底是什么类型,无法获取泛型TClass,所以只能在调用处,通过参数传递进来。

在Kotin中,同样也存在泛型擦除,但是他为了解决这个问题,新增了一个 reified关键字,这个关键字的主要作用就是可以具体化泛型T的类型

我们现在用reified关键字实现一个实例化泛型对象的工具方法,如下:

kotlin 复制代码
inline fun <reified T> newInstance(): T? {
    runCatching {
        // 这里直接可以拿到T的class
        val clazz = T::class.java
        return clazz.newInstance()
    }
    return null
}

可以看到,我们不用像Java一样传递泛型的具体Class,直接可以通过泛型拿到Class。在之前的Kotlin属性委托的巧妙使用-埋点上报封装中,我们就是利用到了这个原理实现了埋点的封装上报,非常的方便有效,是封装库的利器。

因为类型已知,所以正常的操作符如 !is as 现在都能正常使用。那之前的我们封装SharedPreferences保存key-value的那种方式都可以重新利用这个特性进行封装。

注意:reified参数只能用在内联函数中

原理

那为什么会有这个效果呢?我们先看一个例子:

kotlin 复制代码
fun main() {
    getUserInfo("hello refied")
}

inline fun <reified T> getUserInfo(t: T) {
    // 打印泛型的值
    println("start to invoke getUserInfo method.$t")
}

上面是是一段很简单的代码,就是打印泛型t的值。我们反编译最后生成的class类,结果如下:

java 复制代码
public final class Main {
    public static final void main() {
        System.out.println((Object) ("start to invoke getUserInfo method." + ((Object) "hello refied")));
    }
}

从反编译的结果可以看出,调用的方法没有了,反而只有getUserInfo方法内的代码块,而日志中的T,直接替换成了调用方法传递进去的值hello refied

从这可以明白为什么reified要和内联函数一期使用,主要原因就是编译器在编译的时候,会把内联函数的代码替换到调用处。而reified关键字修饰的泛型T,会泛型T直接替换为调用处实际的数据类型。

如果想看更多文章,可以到:

🤙 : 公_众_号:七郎的小院

🥅 : 七郎的_博_客:我的的博客

相关推荐
CYRUS_STUDIO23 分钟前
利用 Linux 信号机制(SIGTRAP)实现 Android 下的反调试
android·安全·逆向
CYRUS_STUDIO42 分钟前
Android 反调试攻防实战:多重检测手段解析与内核级绕过方案
android·操作系统·逆向
黄林晴4 小时前
如何判断手机是否是纯血鸿蒙系统
android
火柴就是我5 小时前
flutter 之真手势冲突处理
android·flutter
法的空间5 小时前
Flutter JsonToDart 支持 JsonSchema
android·flutter·ios
循环不息优化不止5 小时前
深入解析安卓 Handle 机制
android
恋猫de小郭5 小时前
Android 将强制应用使用主题图标,你怎么看?
android·前端·flutter
jctech5 小时前
这才是2025年的插件化!ComboLite 2.0:为Compose开发者带来极致“爽”感
android·开源
用户2018792831675 小时前
为何Handler的postDelayed不适合精准定时任务?
android
叽哥6 小时前
Kotlin学习第 8 课:Kotlin 进阶特性:简化代码与提升效率
android·java·kotlin