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

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

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

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

相关推荐
kk爱闹1 小时前
【挑战14天学完python和pytorch】- day01
android·pytorch·python
每次的天空3 小时前
Android-自定义View的实战学习总结
android·学习·kotlin·音视频
恋猫de小郭3 小时前
Flutter Widget Preview 功能已合并到 master,提前在体验毛坯的预览支持
android·flutter·ios
断剑重铸之日4 小时前
Android自定义相机开发(类似OCR扫描相机)
android
随心最为安4 小时前
Android Library Maven 发布完整流程指南
android
岁月玲珑4 小时前
【使用Android Studio调试手机app时候手机老掉线问题】
android·ide·android studio
杰尼橙子5 小时前
DPDK BPF:将eBPF虚拟机的灵活性带入到了DPDK的高性能用户态
后端·性能优化
国科安芯7 小时前
【AS32系列MCU调试教程】SPI调试的常见问题解析
单片机·嵌入式硬件·性能优化·硬件架构·硬件工程
桦说编程7 小时前
深入解析CompletableFuture源码实现
java·性能优化·源码
oioihoii8 小时前
C++11 forward_list 从基础到精通:原理、实践与性能优化
c++·性能优化·list