技术
Android 技术

Kotlin中reified理解和实战应用

LapisyLapisy
2026年1月10日
32

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

内联函数(inline)

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

1fun main() { 2 getUserInfo { 3 println("get user info.") 4 } 5} 6 7inline fun getUserInfo(block: () -> Unit): String { 8 println("start to invoke getUserInfo method.") 9 block.invoke() 10 return "UserInfo" 11}

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

1public final class Main { 2 public static final void main() { 3 // 调用处,不在是调用函数,而是代码块 4 System.out.println((Object) "start to invoke getUserInfo method."); 5 System.out.println((Object) "get user info."); 6 } 7 8 public static final String getUserInfo(Functions<Unit> block) { 9 Intrinsics.checkNotNullParameter(block, "block"); 10 System.out.println((Object) "start to invoke getUserInfo method."); 11 block.invoke(); 12 return "UserInfo"; 13 } 14}

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

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

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

优点

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

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

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

reified

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

1 public static <T> T newInstance(Class<T> tClass) { 2 try { 3 return tClass.newInstance(); 4 } catch (Exception e) { 5 throw new RuntimeException(e); 6 } 7 }

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

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

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

1inline fun <reified T> newInstance(): T? { 2 runCatching { 3 // 这里直接可以拿到T的class 4 val clazz = T::class.java 5 return clazz.newInstance() 6 } 7 return null 8}

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

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

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

原理

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

1fun main() { 2 getUserInfo("hello refied") 3} 4 5inline fun <reified T> getUserInfo(t: T) { 6 // 打印泛型的值 7 println("start to invoke getUserInfo method.$t") 8}

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

1public final class Main { 2 public static final void main() { 3 System.out.println((Object) ("start to invoke getUserInfo method." + ((Object) "hello refied"))); 4 } 5}

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

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

评论讨论

评论需要审核后才能显示