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直接替换为调用处实际的数据类型。

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

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

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

相关推荐
liang_jy6 小时前
Android SparseArray
android·源码
liang_jy6 小时前
Activity 启动流程扩展篇(一)—— startActivityInner 任务决策全解析
android·源码
NPE~7 小时前
[App逆向]脱壳实战
android·教程·逆向·android逆向·逆向分析
木易 士心8 小时前
别再只会用 drawCircle 了!一文搞懂 Android Canvas 底层机制
android
AtOR CUES9 小时前
MySQL——表操作及查询
android·mysql·adb
怣疯knight10 小时前
安卓App无法增加自定义图片作为图标功能
android
jinanwuhuaguo12 小时前
OpenClaw联邦之心——从孤岛记忆到硅基集体潜意识的拓扑学革命(第二十三篇)
android·人工智能·kotlin·拓扑学·openclaw
We་ct12 小时前
React 性能优化精讲
前端·javascript·react.js·性能优化·前端框架·html·浏览器
Gary Studio13 小时前
安卓HAL C++基础-命名域
android
诸神黄昏EX14 小时前
Android Google XTS
android