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

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

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

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

相关推荐
小白学大数据36 分钟前
高级技术文章:使用 Kotlin 和 Unirest 构建高效的 Facebook 图像爬虫
爬虫·数据分析·kotlin
程序猿会指北1 小时前
【鸿蒙(HarmonyOS)性能优化指南】内存分析器Allocation Profiler
性能优化·移动开发·harmonyos·openharmony·arkui·组件化·鸿蒙开发
丘狸尾1 小时前
[cisco 模拟器] ftp服务器配置
android·运维·服务器
van叶~3 小时前
探索未来编程:仓颉语言的优雅设计与无限可能
android·java·数据库·仓颉
程序猿会指北4 小时前
【鸿蒙(HarmonyOS)性能优化指南】启动分析工具Launch Profiler
c++·性能优化·harmonyos·openharmony·arkui·启动优化·鸿蒙开发
Crossoads7 小时前
【汇编语言】端口 —— 「从端口到时间:一文了解CMOS RAM与汇编指令的交汇」
android·java·汇编·深度学习·网络协议·机器学习·汇编语言
li_liuliu8 小时前
Android4.4 在系统中添加自己的System Service
android
guitarjoy8 小时前
Kotlin - 协程结构化并发Structured Concurrency
kotlin·协程·coroutinescope·结构化同步
C4rpeDime10 小时前
自建MD5解密平台-续
android
鲤籽鲲12 小时前
C# Random 随机数 全面解析
android·java·c#