kotlin|浅谈reified与泛型那些事(代码片段)

嘴巴吃糖了 嘴巴吃糖了     2022-10-23     157

关键词:

背景

在业务中,或者要写某个技术组件时,我们无可避免会经常使用到 泛型 ,从而让代码更具复用性与健壮性。 但相应的,由于Java泛型存在 类型擦除 的实现机制,所以某些情况下就会显得力不从心。

而在 Kotlin 中,由于最终也会被编译为 java字节码 ,所以无可避免也存在着上述问题🙂。

什么是类型擦除?

如下例所示:

Class c1=new ArrayList<Integer>().getClass();
Class c2=new ArrayList<String>().getClass();
// 输出为true
System.out.println(c1==c2);

上述输出结果为什么会true呢?

因为泛型从底层上说是一种语法糖,它只存在于 编译期 。在代码运行期间,jvm会将泛型的相关信息擦除,成功编译后的 class文件 不会包含任何泛型信息。所以在运行时,c1与c2存储的类型都为Object,而 Integer 与 String 已经被擦除。

正是由于类型被擦除,所以就导致一些相关问题,比如不安全的类型转换,模版方法的增多等。

reified

为了解决上述类型擦除问题,以及更好的使用体验。Kotlin 中存在名为 reified 的关键字,它可以被作用于函数上, 以此做到类型擦除后的再生,便于开发者优雅的使用泛型以及获取方法的泛型类型。

Java 中,如果我们要获取函数的泛型类型,一般会通过给函数中传递类型参数的方式,如下所示:

public <T extends Activity> void startActivity(Context context, Class<T> c) 
     context.startActivity(new Intent(context, c));
 

如果上述代码使用 kotlin 来写呢?如下所示:

inline fun <reified C : Activity> Context.startActivityKtx() 
    startActivity(Intent(this, C::class.java))

我们利用 扩展函数 + reified 关键字的方式,减少了模版代码,增强了使用体验。

从而让本该在编译阶段被擦除的Activity类型,能够在运行时获取到。

但需要注意的是,reified 关键字必须和 inline 关键字一起使用(下面会提到为什么)。

inline 关键字是什么呢?

简单理解为:当一个函数被标记为 inline 时,kotlin编译器 会在所有调用这个函数的位置,将方法函数替换为具体的函数体。

解析

通过查看 kotlin 字节码,我们可以得知 reified 的底层实现。

例如下面示例与其对应的字节码:

inline fun <reified C : Activity> Context.toAct() 
    startActivity(Intent(this, C::class.java))


fun test(context: Context) 
    context.toAct<MainActivity>()

// $FF: synthetic method
public static final void toAct(Context $this$toAct) 
   int $i$f$toAct = 0;
   Intrinsics.checkNotNullParameter($this$toAct, "$this$toAct");
   Intrinsics.reifiedOperationMarker(4, "C");
   $this$toAct.startActivity(new Intent($this$toAct, Activity.class));


public static final void test(@NotNull Context context) 
   Intrinsics.checkNotNullParameter(context, "context");
   int $i$f$toAct = false;
   context.startActivity(new Intent(context, MainActivity.class));

我们在 test() 方法中调用toAct(),不难发现,toAct()的逻辑已经被移动到了 test() 中,而我们的泛型类型也被替换为实际使用的类型,从而我们可以在方法函数中直接获取相应的泛型类型。

这也就是为什么 reified 必须要增加 inline ,因为其必须内联才能知道具体类型,从而将我们的实际泛型类型更新到具体的调用代码中,从而完成泛型类型 再生

小提示

Java中无法调用

需要注意的是,reified 无法在java中进行调用,为什么呢?

因为 Java 并没有内联的特性,我们使用的 inline 方法在 Java 中会被当做普通方法,而 reified 正是需要内联才可以保证泛型再生,所以自然无法调用。

从源码上来说,对于reified 关键字的方法,相应的字节码生成时会增加 // $FF: synthetic method 的标记。

如下示例所示:

inline fun <reified C : Activity> Context.toAct() 
   ...

// $FF: synthetic method
public static final void toAct(Context $this$toAct) 
   ...

// $FF: synthetic method

如果你经常写组件,肯定见过这段注释。你可以理解这只是一个标记,其作用为告诉编译器 禁止java代码在编译期访问该方法

比如我们在写 kotlin 组件,而且要同时满足 java 调用时,经常会免不了使用 internal ,即 模块可见 。但相应的,该关键字修饰的方法或者字段在Java中却依然可以被调用,甚是让java调用者费解与不优雅。所以相应的,对于方法,我们可以增加 @JvmSynthetic ,从而避免java代码编译期调用。而 reified 也是正是采用了该思路。

当然也可以采用 @JvmName(name=" xxx") 等方式避免java调用,但其并不是很优雅。

性能方面

使用 reified 不会带来任何性能损失,相反还会增强性能(源于inline)。

之所以这里还要提到,主要是因为如果 内联函数过于复杂,则可能会导致性能问题。所以 reified 的使用其实也需要遵循内联函数的最佳实践。

如果查看Kotlin的标准内联函数,你会发现,代码行数大部分只有1-3行,因为inline会增加代码量的生成,内联函数越复杂,相应的代码量也越高,具体的使用方面,可以参见这篇 Kotlin Vocabulary | 内联函数的原理与应用

参考

Kotlin Vocabulary | Reified: 类型擦除后再生计划

关于我

我是 Petterp ,一个三流开发,如果本文对你有所帮助,欢迎点赞支持,你的支持是我持续创作的最大鼓励!

作者:Petterp
链接:https://juejin.cn/post/7141254785214185503
更多Android学习笔记+视频资料可点击下方CSDN官方认证卡片👇

kotlin泛型③(泛型out协变|泛型in逆变|泛型invariant不变|泛型逆变协变代码示例|使用reified关键字检查泛型参数类型)(代码片段)

文章目录一、泛型out协变二、泛型in逆变三、泛型invariant不变四、泛型逆变协变代码示例五、使用reified关键字检查泛型参数类型本章总结:使用了泛型out协变和泛型in逆变极大的提高了程序的扩展性;泛型in逆变:使用in关键字,可以... 查看详情

kotlin中接口抽象类泛型out(协变)in(逆变)reified关键字的详解(代码片段)

...xff0c;忍不住也分享一下给大家👉点击跳转到教程一、Kotlin中接口的定义Kotlin中接口定义Kotlin规定所有的接口属性和函数实现都要使用override关键字,接口中定义的函数并不需要open关键字修饰,它们默认就是open的。/***... 查看详情

kotlin泛型②(可变参数vararg关键字与泛型结合使用|使用[]运算符获取指定可变参数对象)(代码片段)

文章目录一、可变参数vararg关键字与泛型结合使用二、使用[]运算符获取指定可变参数对象一、可变参数vararg关键字与泛型结合使用如果泛型类型T的参数是vararg可变参数,则在接收可变参数时,需要使用Array<outT>类型的变量进... 查看详情

kotlin泛型vsjava泛型

和Java泛型一样,Kotlin泛型也是Kotlin语言中较难理解的一个部分。Kotlin泛型的本质也是参数化类型,并且提供了编译时强类型检查,实际上也是伪泛型,和Java泛型类型一样。这篇文章将介绍Kotlin泛型里中的重要概... 查看详情

Kotlin 中的 reified 关键字是如何工作的?

】Kotlin中的reified关键字是如何工作的?【英文标题】:HowdoesthereifiedkeywordinKotlinwork?【发布时间】:2018-02-0712:41:26【问题描述】:我试图理解reified关键字的用途,显然是it\'sallowingustodoreflectionongenerics。但是,当我将其省略时,它... 查看详情

关于泛型那些事?

关于泛型那些事? 1.关于泛型和子类继承:      对于以前关于继承而言,其中子类和父类可以进行类型转换,这就是我们常说的类型转换(向上转型和向下转型)。其中我们知道将父类转换为子类需要进行强制... 查看详情

kotlin初学者泛型简单剖析与使用(代码片段)

...#xff1a;CSDN博客专家、华为云·云享专家认证系列专栏:Kotlin初学者学习交流:三人行必有我师焉;择其善者而从之,其不善者而改之。目录一、泛型使用1.1泛型的优点1.2泛型类1.3泛型函数1.4泛型接口二、泛型类型约... 查看详情

[geekband]stl与泛型编程

本篇文章主要介绍泛型算法中的变易、排序、数值算法。一、变易算法所谓变易算法是指那些改变容器中的对象的操作。1.1copy组template <classInputIterator, classOutputIterator> OutputIteratorcopy(InputIteratorfirst... 查看详情

模板与泛型编程(代码片段)

  C++templates的最初发展动机很直接:让我们得以建立“类型安全”的容器如vector,list和map。然而当愈多人用上templates,他们发现templates有能力完成愈多可能的变化。容器当然很好,但泛型编程——写出的代码和其... 查看详情

java泛型泛型简介(泛型类|泛型方法|静态方法的泛型|泛型类与泛型方法完整示例)(代码片段)

...二、泛型类三、泛型方法四、静态方法的泛型五、泛型类与泛型方法完整示例一、泛型简介泛型可以简单理解为参数化类型,主要作用在类,方法,接口上;java泛型与C++模板:Java中的泛型,是仿照C++中的模板开发的,目的是... 查看详情

重载与泛型参数

】重载与泛型参数【英文标题】:Overloadsvsgenericarguments【发布时间】:2010-11-2208:04:09【问题描述】:我有一个问题。在框架中,这主要是在泛型出现之前编写的,你经常看到一个函数有很多重载来做不同类型的事情。a)Parse(intdata... 查看详情

dart学习笔记-枚举与泛型(代码片段)

一、枚举1、枚举是一种有穷序列集的数据类型2、使用关键字enmu定义一个枚举3、常用于代替常量,控制语句等enumSeasonspring,summer,autumn,wintervoidmain()varcurrentSeason=Season.spring;switch(currentSeason)caseSeason.spring:print("1-3月" 查看详情

dart语法篇之类型系统与泛型(代码片段)

...还体现dart中的泛型类型安全上,这一点我会通过对比Kotlin和Dart中泛型实现。你会发现Dart和Kotlin泛型安全完全走不是一个路子,而且dart泛型安全是不可靠的,但是也会发现dart2.0之后对这块做很大的改进。一、可选类... 查看详情

作业09-集合与泛型

...总结1.1以你喜欢的方式(思维导图或其他)归纳总结集合与泛型相关内容。2.书面作业本次作业题集集合1.List中指定元素的删除(题集题目)1.1实验总结。并回答:列举至少2种在List中删除元素的方法。2.统计文字中的单词数量并按... 查看详情

作业09-集合与泛型

...总结1.1以你喜欢的方式(思维导图或其他)归纳总结集合与泛型相关内容。1.2选做:收集你认为有用的代码片段while(true){Stringstr=sc.nextLine();String[]str1=str.split("");if(str1[0]==null)System.out.println("found0results");else{if(map.containsKey(s 查看详情

模板与泛型编程1(函数模板)(代码片段)

定义、实例化函数模板:对于函数体完全相同,唯一差异就是参数类型的情况,我们可以定义一个通用的函数模板,而非为每个类型都定义一个新函数:1#include<iostream>2#include<vector>3usingnamespacestd;45template<typenameT>//模... 查看详情

201621044079韩烨作业09-集合与泛型

作业09-集合与泛型1.本周学习总结1.1以你喜欢的方式(思维导图或其他)归纳总结集合与泛型相关内容。1.2选做:收集你认为有用的代码片段2.书面作业本次作业题集集合1.List中指定元素的删除(题集题目)1.1实验总结。并回答:列... 查看详情

java示例代码_什么';通配符背后的目的是什么?它们与泛型有何不同

java示例代码_什么';通配符背后的目的是什么?它们与泛型有何不同 查看详情